【问题标题】:How does the compiler handle case statements?编译器如何处理 case 语句?
【发布时间】:2017-03-19 08:12:11
【问题描述】:

the documentation 的 case 语句下,它说:

caseList 表示的每个值在 case 中必须是唯一的 声明;

以及显示的示例,

case I of
  1..5: Caption := 'Low';
  6..9: Caption := 'High';
  0, 10..99: Caption := 'Out of range';
else
  Caption := ;
end

相当于嵌套条件:

if I in [1..5] then
  Caption := 'Low';
else if I in [6..10] then
  Caption := 'High';
else if (I = 0) or (I in [10..99]) then
  Caption := 'Out of range'
else
  Caption := ;

所以第一个引用表明它像一组一样处理(阅读 cmets here 至少有一个人和我在一起)。

现在我知道了

其中 selectorExpression 是任何更小的序数类型的表达式 超过 32 位

与集合的属性相矛盾,因为它在集合下her 提到:

基本类型最多可以有 256 个可能的值,并且它们的 序数必须在 0 到 255 之间

真正困扰我的是为什么必须在 caseList 中具有唯一值。如果它等价于if 语句,那么第二个值将不会被测试,因为编译器已经找到了先前的匹配项?

【问题讨论】:

  • 没有矛盾。有效的 case 语句总是可以写成等效的 if 语句。但是 if 语句更通用。如果选择器值对于一个集合来说太大了,那么可以使用比较运算符来编写它们。不要挂断文档中的 if 语句。它只是为了说明这一点。
  • 我尝试了一个测试示例,当在调试器模式中(使用 f7 时)在 case 语句中它直接跳转到匹配值,但在 if 语句中使用 F7 时它是逐条指令执行,我对组装了解不多,但是在处理第一个案例之前发生了很多事情。
  • 编译器会为一个case语句生成不同的代码。但行为等价于 if。
  • 这里参考跳转表:books.google.co.uk/…
  • @DavidHeffernan 这就是我问的原因。从约翰的回答我得到(不确定)跳转表是在处理第一个案例之前发生的事情

标签: delphi if-statement switch-statement


【解决方案1】:

文档采用 specific case 语句,该语句等效于 specific if 语句。

一般来说,任何 case 语句都可以使用相同的方法重写为 if 语句。然而,反之则不成立。

文档使用等效的 if 语句来解释 case 语句的逻辑行为(或语义)。它不是编译器内部工作的表示。

编译器如何处理case语句?

首先注意这个问题有两个方面。

  • 语义编译器必须按照文档中的说明处理 case 语句。这包括:
    • 确保可以在编译时评估每个 caseList 条目的值。
    • 确保 caseList 条目是唯一的。
    • 无论哪个 caseList 条目匹配,都会调用相应的 caseList 语句。
    • 如果没有 caseList 条目匹配,则调用 else 语句。
  • 但是,如果优化后的字节/机器代码在逻辑上是等效的,编译器有权优化其认为合适的实现。
    • Johan's answer 描述了常见的优化:跳转列表和重新排序。
    • 考虑到严格的语义,这些更容易应用。

真正困扰我的是为什么必须在 caseList 中具有唯一值。

需要唯一性来消除歧义。如果有多个匹配项,应该使用哪个 caseList-statement?

  • 它可以调用第一个匹配的 caseList 语句并忽略其余的。 (SQL Server CASE 语句的行为如下。)另请参阅下面的[1]
  • 它可以调用所有这些。 (如果我没记错的话,MANTIS 编程语言在其 case 语句的版本中使用了这种语义。)
  • 它可能会报告一个错误,要求程序员消除 caseList 的歧义。 (简单地说,这就是 Delphi 的规范所要求的。许多其他语言使用相同的方法。对此争论不休是徒劳的,尤其是因为这种选择不太可能成为阻碍。)

如果它等同于 if 语句,则第二个值将不会被测试,因为编译器已经找到了先前的匹配项。

[1] 我想指出这会使代码更难阅读。当使用魔法文字时,这种行为是“好的”,但当使用 const 标识符时,它变得很危险。如果 2 个不同的 const 具有相同的值,则不会立即调用 caseList 也匹配的后一个 caseList 语句。由于简单的 caseList 重新排序,case 语句也会发生行为变化。

const
  NEW_CUSTOMER = 0;
  EDIT_CUSTOMER = 1;
  ...
  CANCEL_OPERATION = 0;

case UserAction of
  NEW_CUSTOMER : ...;
  EDIT_CUSTOMER : ...;
  ...
  CANCEL_OPERATION : ...; { Compiler error is very helpful. }
end;

与集合的性质相矛盾

没有矛盾。每个 caseList 值必须是唯一的这一事实并不意味着它必须“像集合一样处理”。那是你的错误假设。其他人做同样的假设也是不正确的。

如何检查唯一性约束取决于编译器。我们只能推测。但我猜最有效的方法是维护一个有序的范围列表。遍历每个 caseList 的值和范围,找到它在上述列表中的位置。如果重叠,则报错,否则添加到列表中。

【讨论】:

  • @David 我已回滚您的编辑。我不打算使用水平规则。我试图模拟脚注参考(来自项目符号“另见下面的**。”)。我还没有找到降价支持。
  • 抱歉,没注意
【解决方案2】:

案例陈述
编译器更喜欢将 case 语句转换为跳转表。
为了使这种情况成为可能,不允许使用重复的案例标签。

此外,编译器不必按照您声明它们的顺序来测试 case 语句。出于优化原因,它会根据需要重新排列这些元素;所以即使它不使用跳转表,它仍然不能允许重复的大小写标签。

正是出于同样的原因(并且为了让程序员保持头脑清醒),case 语句不允许失败。

if 语句
一个(一系列)if 语句按照它们声明的顺序进行处理。
编译器会一一测试(并在适当的时候跳出)。
如果 if 语句不包含重复项,则代码将执行与等效 case 语句相同的操作,但生成的代码很可能会有所不同。
If 语句永远不会转换为跳转表。

关于套装
集合限制为 256 个元素的原因是集合中的每个元素都占用一位空间。
256 位 = 32 字节。
在一个集合中允许超过 256 个元素会过多地增加内存中的表示,从而影响性能。

case 语句不使用集合,它只是在其语义中使用集合逻辑。

【讨论】:

  • 虽然严格来说,跳表优化仍然可以通过将第一个匹配目标加载到表中来使用。但总的来说,我确实同意唯一性约束确实使优化更容易。
  • 我真的对你的回答很感兴趣,你能否提供更多关于“关于集合”部分的详细信息,尤其是“256 位 = 32 字节”,你的意思是在使用“如果在 Aset 中的值那么...." 编译器正在测试一个位,而不是在大厅集合中搜索匹配项。
  • 编译器可以随心所欲。例如,它可以使用两个比较运算符来测试in [1..5],这可能是最佳方法。是什么推动了这个问题?它现在似乎正在蔓延。它从案例陈述开始,现在您想了解集合。
  • @DavidHeffernan 它仍然是关于案例陈述,我只是对答案中的那部分感到好奇,你说得对,我认为这需要我提出一个新问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-03-13
  • 2017-02-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-18
  • 1970-01-01
相关资源
最近更新 更多