【问题标题】:C++ switch statement expression evaluation guaranteeC++ switch 语句表达式求值保证
【发布时间】:2015-09-21 06:14:13
【问题描述】:

关于开关,标准规定如下。 “当 switch 语句被执行时,它的条件被评估并与每个 case 常量进行比较。”

是否意味着条件表达式只计算一次,并且每个编译器都有标准保证?

例如,当一个函数用在switch语句头部时,会有副作用。

int f() { ... }
switch (f())
{
    case ...;
    case ...;
}

【问题讨论】:

  • 这就像询问int main() { putchar('c'); } 是否保证只调用一次putchar(因此保证只打印一个'c')。真的,我不知道标准中有任何提供这种保证的文本。
  • 我不同意。 switch 是一个特殊的语句,它可以在汇编中以几种有效的方式实现。对我来说,这并不明显。
  • 我认为 cpplearner 是对的,“as-if”规则允许“几种有效的组装方式”。除此之外,保证您的函数调用(具有副作用)被评估一次,在 C++ 中的每个上下文中,都是相同的,无论是哪种上下文。
  • @mikk:你能想出一个优化器会多次评估表达式的好理由吗?
  • @YvesDaoust:当然不是 :) 但问题是它是否得到保证。

标签: c++ standards language-lawyer


【解决方案1】:

我认为可以保证f 只被调用一次。

首先我们有

条件应为整数类型、枚举类型或类类型。

[6.4.2 (1)](非积分的东西在这里不适用),和

作为表达式的条件的值是 表达

[6.4 (4)]。此外,

条件的值将被简称为“条件”,其中 用法是明确的。

[6.4 (4)] 这意味着在我们的例子中,“条件”只是int 类型的普通值,而不是ff 仅用于查找条件的值。现在当控制到达switch 语句时

评估它的条件

[6.4.2 (5)],即我们使用f 返回的int 的值作为我们的“条件”。然后最后条件(它是int 类型的值,而不是f)是

与每个 case 常量比较

[6.4.2 (5)]。这不会再次触发来自f 的副作用。

来自 N3797 的所有报价。 (也查过N4140,没区别)

【讨论】:

  • N3797 是中间草案,C++14 考虑使用 N4139/N4140,C++11 考虑使用 N3337
  • @MattMcNabb 它们是免费提供的吗? (对于这个问题,它们有什么不同吗?)
  • 我认为 OP 的歧义源于将句子解析为 its condition is evaluated (and compared with each case constant)(its condition is evaluated and compared with) each case constant 与条件的输入无关。
  • 表达式可能是integral type - 我认为您没有引用相关部分。
  • @pqnet 好吧,从某种意义上说,如果我写的是真的,我们会比较整数,这不会有副作用。所以它会回答这个问题。
【解决方案2】:

阅读N4296

第 10 页第 14 段:

与完整表达式相关的每个值计算和副作用都在每个值之前排序 与要评估的下一个完整表达式相关的计算和副作用。

当我读到段落的第一行时。 10(以上):

完整表达式是不是子表达式的表达式 另一个表达式。

我必须相信switch 语句的条件是一个完整的表达式,并且每个条件表达式都是一个完整的表达式(尽管在执行时是微不足道的)。

switch 是语句而不是表达式(参见 6.4.2 和许多其他地方)。

因此,通过读取switch 的评估必须在case 常量的评估之前进行。

许多观点归结为对规范的曲折阅读才能得出明显的结论。

如果我对这句话进行同行评审,我会提出以下修改(粗体):

当 switch 语句被执行时,它的条件被评估 每次执行 switch 语句一次并与每个 case 常量进行比较。

【讨论】:

  • 为什么你认为switch语句中的控制表达式不是一个完整的表达式?显然它是一个表达式,那么您认为它属于哪个更大的表达式?
  • @BenVoigt 我不敢相信这不是一个完整的表达,因为你给出的原因。我已经编辑了我的句子。希望这能让它更清楚。
  • 如果你可以改写以避免双重否定它会有所帮助(特别是因为这两个部分是分开的)。
  • 从 C++11 开始,完整表达式被定义为“……一个不属于另一个完整表达式的表达式(例如……if/switch 的条件表达式.. .)”。 -- en.cppreference.com/w/cpp/language/eval_order
  • @Avi 我引用了 C++14 标准的最新草案,我相信它是正确的。虽然 cppreference.com 是一个很棒的网站,但它并不完全是标准的直接引用。因此,如果我们处于语言律师模式,则不可接受。但这一切都归结为同一件事。 switch 的条件表达式是一个完整的表达式,并且在其副作用之前评估了完整的 y。如果添加或删除案例陈述可能会产生副作用,那么外部语言律师会很疯狂!
【解决方案3】:

是的,当 switch 语句执行时,表达式只计算一次:

§ 6.4 选择声明

4 [...] 作为表达式的条件的值是 表达式 [...] 条件的值将被简称为“条件”,其用法是明确的。

这意味着对表达式求值,并将其值视为要针对每个 case 语句求值的 condition

【讨论】:

    【解决方案4】:

    第 6.4.4 节:

    ...作为表达式的条件的值是 表达式,上下文转换为布尔语句,而不是 switch;...条件的值将被简称为“条件”,其中 用法很明确

    在我的理解中,上面的引用相当于下面的伪代码:

    switchCondition := evaluate(expression)
    

    现在添加您的报价

    ...对其条件进行评估并与每个 case 常量进行比较。

    应该翻译成:

    foreach case in cases
        if case.constant == switchCondition
             goto case.block
    

    是的,看起来就是这样。

    【讨论】:

      【解决方案5】:

      此代码是否打印一次或两次hello

      int main() {
          printf("hello\n");
      }
      

      嗯,我认为答案在于对标准描述内容的更一般理解,而不是具体的switch 声明措辞。

      根据 Program execution [intro.execution],该标准描述了一些 抽象机器 执行根据C++ 语法。它并没有真正定义“抽象机器”或“执行”是什么意思,但它们被认为是指它们明显的计算机科学概念,即一台通过抽象语法树并根据描述的语义评估它的每个部分的计算机标准。这意味着如果你写了一次,那么当 执行 到达那个点时,它只被评估一次。

      更相关的问题是“什么时候实现可以评估一些不是程序中编写的方式”?为此,存在 as-if 规则和一堆未定义的行为,允许实现偏离这种抽象解释。

      【讨论】:

        【解决方案6】:

        C++ '20 澄清了这个问题,明确说明条件被评估一次:

        当 switch 语句被执行时,它的条件被评估。如果其中一个 case 常量的值与条件相同,则将控制权传递给匹配的 case 标签后面的语句。

        change 的提交消息承认它之前可能令人困惑:

        [stmt.switch] 澄清案例标签的比较

        【讨论】:

          【解决方案7】:

          表达式保证只被控制流计算一次。这在标准 N4431 §6.4.2/6 中是合理的。 switch 语句 [stmt.switch]强调我的):

          大小写和默认标签本身不会改变 控制,在这些标签上继续畅通无阻。要退出 开关,见中断,6.6.1。 [注:通常,子语句是 开关的主题是复合的,出现大小写和默认标签 关于(复合)中包含的顶级语句 子语句,但这不是必需的。声明可以出现在 switch 语句的子语句。 ——尾注]

          【讨论】:

          • 我认为这句话只定义了隐含的失败。
          • @BaummitAugen 恕我直言,标准中的上述引用隐含明确的理由,即条件将只评估一次,否则将违反流控制。
          猜你喜欢
          • 2016-10-27
          • 2020-05-10
          • 1970-01-01
          • 2011-03-28
          • 2014-11-06
          • 2011-05-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多