【问题标题】:Is there a useful case using a switch statement without braces?有没有使用不带大括号的 switch 语句的有用案例?
【发布时间】:2011-12-28 09:57:34
【问题描述】:

在 H&S5 中,我遇到了不使用大括号的“最奇怪”的 switch 语句(8.7.1,第 277 页)。
这是示例:

switch (x)
    default:
    if (prime(x))
        case 2: case 3: case 5: case 7:
            process_prime(x);
    else
        case 4: case 6: case 8: case 9: case 10:
            process_composite(x);

这个想法似乎是为了避免 prime(x) 对于最常见的小数字的开销。

当我看到那个语句时,我对缺少大括号感到困惑,但检查官方语法(C1X pre-standard,6.8.4,第 147 页),语法是正确的:switch 语句在switch 表达式和右括号。

但在我的编程实践中,我再也没有遇到过如此奇怪的 switch 语句(而且我不想在代码中看到任何我必须负责的代码),但我开始怀疑:

你们有谁知道这样一个 switch 表达式,一个不使用大括号但仍然有意义的表达式?不只是 switch (i);(这是合法的,但 NOP),而是使用至少两个具有某种有用目的的 case 标签?

【问题讨论】:

  • 你的“最离奇”switch语句是Duff's Device吗?
  • 不,不是。我将为那些无法访问 H&S5 的人添加代码示例。达夫的装置需要大括号!
  • 很好的例子。我不认为缺少大括号是特殊性,在这里。您的示例也适用于大括号。这里不同寻常的是让switch 标记语句的不同逻辑级别。
  • @JensGustedt 是的,它可以与大括号一起使用,但它是迄今为止我见过的唯一一个在没有它们的情况下也可以工作并且似乎仍然有用的方法。
  • 为了便于记录,“H&S5”是 Samuel Harbison 和 Guy Steele 的 C: A Reference Manual 的第五版。 (我不知道,只好通过搜索代码片段来追踪它。)

标签: c language-lawyer


【解决方案1】:

如果您在宏中使用控制结构,switch 而不是if 会很方便,因为它没有悬空else 问题。

#define DEBUG_PRINT(...) switch (!debug_mode) case 0: fprintf(__VA_ARGS__)

如果该宏的用户将其置于附加条件中,您不会感到意外

if (unclear) DEBUG_PRINT(stderr, "This is really %unclear\n", unclear);
else {
 // do something reasonable here
}

这样的调试宏具有总是被编译(然后最终被优化)的优点。所以调试代码必须在程序的整个生命周期内保持有效。

这里还要注意switch 不使用{} 很重要,否则if/else 示例也不起作用。所有这些都可以通过其他方式(if/else(void)0do/while 技巧)实现,但这是我所知道的最方便的方式。

不要误会我的意思,我并不是说每个人都应该在宏中使用控制结构,你当然应该知道自己在做什么。但在某些情况下这是合理的。

【讨论】:

  • +1 不错,从没见过这样的。我会在末尾添加default: 标签,但这不是必需的。但是:即使开关使用大括号,if/else 配对仍然是正确的恕我直言。
  • @JohanBezem,不,我认为这是不正确的,因为这会扩展为 { ... }; 之类的东西,因此 if 整体上将被 ; 终止/跨度>
  • 您的示例中的“switch”语句与do{this;that;the_other;}while(0) 模式相比有什么好处吗?后者是我认为在无法将伪函数宏编写为虚拟右值的情况下编写伪函数宏的常规惯用方式(例如#define begin_write(x) (write_mode=1,begin_read_or_write((x)))
  • do{...}while(0) 中包装一个宏意味着可以在宏中编写多个语句,而不必担心它如何与外部代码进行语法交互。当我在任何宏的开头看到“do{}”时,我会查找“...while”;如果是“while(0)”,我会将 do/while 构造解释为“语句保护”。在在 DEBUG_PRINT 宏的情况下,可以使用 && 或 ?: 运算符,因此整个宏将是一个表达式而不是一个语句(不确定哪种公式会产生最好的代码而没有多余的警告)。
  • 我不清楚在什么情况下“switch”语句会比使用条件运算符或 do/while 保护更好。前者意味着宏可以嵌入到更大的表达式中,而后者则允许将宏扩展为包含多个语句而没有困难。开关公式没有提供这些优点。它是否提供了一些我没有看到的其他优势?
【解决方案2】:

这是 Dennis Ritchie 于​​ 1972 年在 his work on the first C compiler 期间编写的示例。链接在我刚刚链接到的页面底部的 c02.c 模块包括

easystmt()
{
    extern peeksym, peekc, cval;

    if((peeksym=symbol())==20)  /* name */
        return(peekc!=':');  /* not label */
    if (peeksym==19) {      /* keyword */
        switch(cval)
        case 10:    /* goto */
        case 11:    /* return */
        case 17:    /* break */
        case 18:    /* continue */
            return(1);
        return(0);
    }
    return(peeksym!=2);     /* { */
}

从阅读他 1972 年的代码可以看出,Dennis 很喜欢 switch 语句 - 他经常使用它们。这并不奇怪,因为几乎所有内容都被编码为 int 部分是因为缺乏其他数据类型的可能性。他的编译器实现在那个阶段没有使用结构,因为他只是在将它们添加到语言中。动态调度、vtables 和多态性还有很长的路要走。我已经尝试过但未能找到参考,但如果我没记错的话,丹尼斯“发明”了 switch 语句,或者至少贡献了导致它们在 C 中采用的形式的想法,并且 认为它们是他最好或最自豪的补充之一语言

省略大括号的能力使得 switch 语句在形式上类似于iffordowhile 语句,有助于简化和统一语法。请参阅 C 语法中的选择语句和迭代语句产生式(例如在 Kernighan 和 Ritchie 的附录 A13 中,我的副本中的第 236-237 页),其中定义了这些内容。

显然,人们总是可以添加大括号,但对于像这个这样简单的例子来说,这可能看起来很重。这个例子可以被编码为一个分离的 if 语句,但我认为 Dennis 对 switch 的想法之一是更清楚地为编译器提供了基于所涉及的特定常量优化分支逻辑实现的机会。

【讨论】:

    【解决方案3】:

    我想到了另一个案例。

    假设我有一个 unsigned char 类型的计数器,指示循环的迭代次数,但如果计数器等于 0,它需要循环 256 次。如果我的想法是正确的,你可以这样编码:

    uint8_t counter;
    /* counter will get its value here somewhere */
    switch (counter)
        default:
            while (0 < counter)
            {
                case 0:
                    /* Perform action */
                    counter--;
            }
    

    这当然假设从 0x00 的下溢导致 unsigned char 的 0xFF。但它适用于我所有的环境,即使 PC Lint 会抱怨...... 是的,它包含大括号,但仅适用于while,不适用于switch。如果你知道更好的事情,请告诉我!

    我会这样编程吗?绝不! ...好吧,我什至可能在一个小型 8 位处理器上! :-)

    【讨论】:

    • 是的,我知道你可以使用 do/while 来达到同样的效果,但这不是这个问题的重点......
    • 嗯,我认为你对非串行控制流更着迷,而不是这只是一个语句而不是一个块。这又可以用封闭的{} 来完成,不是吗?
    • 是的,它可以,就像最初的 H&S5 示例一样。我正在努力为真正有经验的 C 程序员创建一个特殊的培训,为此我正在研究语言的边缘及其他方面。
    • 啊,我明白了。但是为此,在不同级别的控制结构中跳转比您在问题中提出的是否使用大括号更重要。
    • 好吧,我假设我的目标受众知道在不同级别的控制结构中跳跃是可能的。在过去的某个时间,即使不是很多,也有几个甚至会这样做。但是沉迷于常规和个人的编程风格,你往往会忽视基础知识。或者你最近是否完整地重新阅读了 C BNF 语法,记录了所有的后果,无论它们是好是坏?我没有... *咧嘴*我想通过使用一些引人注目的插图来唤醒我的观众,即使(或特别是)它们被认为是糟糕的风格。
    【解决方案4】:

    第 6.8.4.2 节 switch 语句说:

    switch 语句使控制跳转到、进入或越过 作为 switch 主体的语句,取决于 a 的值 控制表达式,并在存在默认标签和 开关体上或开关体中的任何大小写标签的值。案例或违约 标签只能在最近的封闭开关内访问 声明。

    switch-bodyclosest enclosure switch-statement 似乎不需要大括号。 所以你是对的,它看起来很奇怪,但是是合法的。 (以前没见过)

    【讨论】:

    • 当然,大括号通常是必需的,而且非常有用。但没有必要。我添加了样本。属于switch(x) 的案例标签可以放置在process_composite(x) 之后的分号之前,即指示开关主体的结尾。
    【解决方案5】:

    出于可读性原因,实际上开关与大括号一起使用(即使在 Duff 的设备中)。而且加大括号也没有坏处。

    【讨论】:

    • 这不是问题,抱歉。我知道这一点,并且我不会接受我职责范围内的任何其他代码。但是,我对语言定义的边缘感兴趣。
    猜你喜欢
    • 2022-01-20
    • 2011-04-08
    • 1970-01-01
    • 2010-10-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-22
    相关资源
    最近更新 更多