编译器如何处理case语句?

4
文档的case语句中,它说:

在case语句中,每个由caseList表示的值必须是唯一的;

并且展示了一个示例:

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 := ;

所以第一段引述表明它被处理为一个集合(至少有一个人在这里与我持相同看法,请阅读评论)。
现在我知道了部分内容:

当selectorExpression是小于32位的序数类型表达式时

这与集合的属性相矛盾,在此处关于集合中提到:

基础类型最多只能有256个可能的值,并且它们的序号必须在0和255之间

让我真正感到困扰的是为什么caseList中需要唯一值。如果它等同于if语句,因为编译器已经找到了先前的匹配,所以不会测试第二个值,那么第二个值将不再需要测试吗?

1
没有矛盾。有效的 case 语句总是可以写成等价的 if 语句。但是,if 语句更通用。如果选择器的值太大而无法使用集合,则可以改用比较运算符来编写它们。不要被文档中的 if 语句所困扰。它只是为了说明这一点而存在的。 - David Heffernan
1
编译器将为case语句生成不同的代码。但是其行为等效于if语句。 - David Heffernan
@DavidHeffernan 这就是我提问的原因。从Johan的回答中,我得出(不确定)跳转表是在处理第一个case之前发生的事情。 - Nasreddine Galfout
这就是我要问的。你想语义上了解case语句是什么吗?还是性能是问题所在?现在驱动这个问题的因素一点也不清楚。你似乎在问问题中是否case和if语句等价。这将意味着语义很重要。但现在我想知道你的问题是否不同。 - David Heffernan
显示剩余7条评论
2个回答

10

这份文档使用一个特定的case语句来等同于一个特定的if语句。

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

该文档使用等效的if语句来解释case语句的逻辑行为(或语义)。这并不代表编译器在内部执行的情况。

编译器如何处理case语句?

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

  • 从语义上讲,编译器必须按照文档中指示的方式处理case语句。这包括:
    • 确保可以在编译时评估每个caseList条目的值。
    • 确保caseList条目是唯一的。
    • 与之匹配的任何caseList条目都会调用相应的caseList语句。
    • 如果没有caseList条目匹配,则调用else语句。
  • 然而,只要优化后的字节/机器代码在逻辑上等效,编译器就有权优化实现。
    • Johan's answer描述了常见的优化:跳转列表和重新排序。
    • 由于严格的语义,应用这些优化要容易得多。

真正困扰我的是,为什么在caseList中需要唯一的值。

唯一性是为了消除歧义。如果有多个caseList语句匹配,应使用哪一个?

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

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

[1] 我想指出,这可能会使代码变得更难阅读。当使用const标识符时,这种行为是"ok"的,但是当使用魔法字面量时,却变得危险。如果两个不同的consts具有相同的值,很难立即看出后面的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,我已经撤销了你的编辑。我并不想要一条水平线。我试图模拟一个脚注引用(来自于“另请参见下面的 **”项目符号)。我还没有找到支持这种格式的标记语言。 - Disillusioned
抱歉,我没有注意到。 - David Heffernan

6

case语句
编译器更喜欢将case语句转换为跳转表。
为了实现这一点,不允许有重复的case标签。

此外,编译器不必按照声明顺序测试case语句。出于优化原因,它会根据自己的判断重新排序这些元素;因此,即使它不使用跳转表,仍然不能允许重复的case标签。

出于同样的原因(并为了保持程序员的心理健康),case语句不允许贯穿。

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

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

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


严谨地说,通过将第一个匹配目标加载到表中仍然可以使用跳转表优化。但总的来说,我同意唯一性约束确实使优化变得更容易。 - Disillusioned
我对你的回答非常感兴趣,能否请您详细说明“关于集合”的部分,特别是“256位=32字节”,您是否意味着当使用“if value in Aset then ....”时,编译器只测试一个位而不是在整个集合中搜索匹配项。 - Nasreddine Galfout
编译器可以自由地决定如何处理它。例如,它可以使用两个比较运算符来测试“in [1..5]”,这可能是最优的方法。驱动这个问题的是什么?现在似乎变得很复杂了。它始于case语句,现在你想知道关于集合的事情。 - David Heffernan
@DavidHeffernan 还是关于 case 语句的问题,我只是对答案中的那一部分感到好奇,你说得对,我认为这需要我提一个新的问题。 - Nasreddine Galfout

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接