Switch语句:必须默认为最后一种情况?

tan*_*ius 168 c switch-statement

请考虑以下switch声明:

switch( value )
{
  case 1:
    return 1;
  default:
    value++;
    // fall-through
  case 2:
    return value * 2;
}
Run Code Online (Sandbox Code Playgroud)

此代码编译,但它对C90/C99有效(=已定义的行为)?我从未见过默认情况不是最后一种情况的代码.

编辑:
正如Jon CageKillianDS所写:这是非常丑陋和令人困惑的代码,我很清楚它.我只对一般语法(它定义了吗?)和预期输出感兴趣.

Sal*_*lil 83

case语句和default语句可以在switch语句中以任何顺序发生.default子句是一个可选子句,如果case语句中没有任何常量可以匹配,则匹配该子句.

好例子 :-

switch(5) {
  case 1:
    echo "1";
    break;
  case 2:
  default:
    echo "2, default";
    break;
  case 3;
    echo "3";
    break;
}


Outputs '2,default'
Run Code Online (Sandbox Code Playgroud)

非常有用,如果你希望你的案例在代码中以逻辑顺序呈现(如,不是说案例1,案例3,案例2 /默认),你的案件很长,所以你不想重复整个案例底部的代码为默认值

  • 这正是我通常将默认放置在除结尾之外的某个地方的情况......对于显式情况(1,2,3)有一个逻辑顺序,我希望默认行为与其中一个显式情况完全相同不是最后一个. (7认同)

Sec*_*ure 78

C99标准没有明确说明这一点,但将所有事实放在一起,它是完全有效的.

A casedefault标签等同于goto标签.请参见6.8.1标记语句.特别有趣的是6.8.1.4,这使得已经提到过的Duff设备:

任何语句都可以在前缀之前声明标识符作为标签名称.标签本身并不会改变控制流程,而这种控制流程在它们之间继续畅通无阻.

编辑:开关内的代码没什么特别的; 它是一个正常的代码块,如在if-statement中,带有额外的跳转标签.这解释了坠落行为以及为什么break有必要.

6.8.4.2.7甚至给出了一个例子:

switch (expr) 
{ 
    int i = 4; 
    f(i); 
case 0: 
    i=17; 
    /*falls through into default code */ 
default: 
    printf("%d\n", i); 
} 
Run Code Online (Sandbox Code Playgroud)

在人工程序片段中,标识符为i的对象存在自动存储持续时间(在块内)但从未初始化,因此如果控制表达式具有非零值,则对printf函数的调用将访问不确定的值.同样,无法访问函数f的调用.

case常量在switch语句中必须是唯一的:

6.8.4.2.3每个case标签的表达式应为整型常量表达式,同一switch语句中的两个case常量表达式在转换后不应具有相同的值.switch语句中最多可能有一个默认标签.

评估所有案例,然后跳转到默认标签,如果给定:

6.8.4.2.5对控制表达式执行整数提升.每个case标签中的常量表达式将转换为控制表达式的提升类型.如果转换后的值与提升的控制表达式的值匹配,则控制将跳转到匹配的案例标签后面的语句.否则,如果存在默认标签,则控制将跳转到带标签的语句.如果没有转换的大小写常量表达式匹配且没有默认标签,则不会执行开关主体的任何部分.

  • 英特尔告诉您将最频繁的代码放在[分支和循环重组以防止错误预测]的开关语句中(https://software.intel.com/en-us/articles/branch-and-loop-reorganization-to - 防止-错误预测).我在这里是因为我有一个`默认'案例,主导其他案例大约100:1,我不知道它的有效或未定义是否使`default`成为第一种情况. (10认同)
  • @HeathHunnicutt你显然不明白这个例子的目的.代码不是由这张海报编写的,而是直接从C标准中提取,作为奇怪的switch语句的例子,以及糟糕的实践将如何导致错误.如果您不厌其烦地阅读代码下方的文字,那么您也会意识到这一点. (6认同)
  • +1以补偿downvote.从引用C标准中贬低某人似乎非常苛刻. (2认同)
  • @Lundin我没有对C标准进行投票,我没有像你建议的那样忽视任何事情.我贬低了使用一个坏的,不需要的例子的坏教学法.特别是,该示例完全涉及与被问及的不同情况.我可以继续,但"谢谢你的反馈." (2认同)

kri*_*iss 47

它在某些情况下是有效且非常有用的.

请考虑以下代码:

switch(poll(fds, 1, 1000000)){
   default:
    // here goes the normal case : some events occured
   break;
   case 0:
    // here goes the timeout case
   break;
   case -1:
     // some error occurred, you have to check errno
}
Run Code Online (Sandbox Code Playgroud)

关键是上面的代码比级联更具可读性和效率if.您可以将默认设置放在最后,但它没有意义,因为它会将注意力集中在错误情况而不是正常情况(这是这种default情况).

实际上,这不是一个很好的例子,在投票中你知道最多可能发生多少事件.我真正的问题是,有有定义的一组输入值的情况下,也有"例外"和正常的情况下.如果最好将异常或正常情况放在前面是一个选择问题.

在软件领域,我想到了另一个非常常见的情况:带有一些终端值的递归.如果您可以使用开关表达它,default将是包含递归调用和区分元素(个别情况)终端值的常用值.通常无需关注终端价值.

另一个原因是案例的顺序可能会改变编译的代码行为,这对性能很重要.大多数编译器将按照与交换机中出现的代码相同的顺序生成编译的汇编代码.这使得第一种情况与其他情况大不相同:除了第一种情况之外的所有情况都将涉及跳转并且将清空处理器流水线.你可能会理解它像分支预测器默认运行交换机中第一个出现的情况.如果一个案例比其他人更常见那么你有很好的理由把它作为第一个案例.

阅读评论这是在阅读英特尔编译器分支循环重组关于代码优化后原始海报提出问题的具体原因.

然后将成为代码可读性和代码性能之间的一些仲裁.可能更好地发表评论向未来的读者解释为什么案例首先出现.

  • 给出(良好)示例的+1,没有穿透行为. (5认同)

Jen*_*edt 15

是的,这是有效的,在某些情况下它甚至是有用的.通常,如果您不需要它,请不要这样做.

  • @John Cage:在这里给我一个-1是令人讨厌的.这是有效的代码并不是我的错. (23认同)
  • 有时当切换我们从某个系统函数得到的错误时.假设我们有一个案例,我们知道我们必须做一个干净的退出,但这个干净的退出可能需要一些我们不想重复的编码行.但是假设我们还有很多其他奇特的错误代码,我们不想单独处理.我会考虑在默认情况下放置一个perror并让它运行到另一个case并干净地退出.我不是说你应该这样做.这只是一个品味问题. (4认同)

Pat*_*ter 8

switch语句中没有定义的顺序.您可以将案例视为命名标签,如goto标签.与人们在这里想到的相反,在值为2的情况下,默认标签不会跳转到.用一个经典的例子来说明,这里是Duff的设备,它是switch/caseC中极端的典型代表.

send(to, from, count)
register short *to, *from;
register count;
{
  register n=(count+7)/8;
  switch(count%8){
    case 0: do{ *to = *from++;
    case 7:     *to = *from++;
    case 6:     *to = *from++;
    case 5:     *to = *from++;
    case 4:     *to = *from++;
    case 3:     *to = *from++;
    case 2:     *to = *from++;
    case 1:     *to = *from++;
            }while(--n>0);
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 对于那些不熟悉Duff设备的人来说,这段代码是完全不可读的...... (2认同)

sup*_*cat 7

我认为将一个'default'放在case语句结尾之外的某个地方的一个场景是在状态机中,其中一个无效状态应该重置机器并继续进行,就好像它是初始状态一样.例如:

switch(widget_state)
{
  default:  /* Fell off the rails--reset and continue */
    widget_state = WIDGET_START;
    /* Fall through */
  case WIDGET_START:
    ...
    break;
  case WIDGET_WHATEVER:
    ...
    break;
}

另一种安排,如果无效状态不应重置机器但应易于识别为无效状态:

switch(widget_state) { case WIDGET_IDLE: widget_ready = 0; widget_hardware_off(); break; case WIDGET_START: ... break; case WIDGET_WHATEVER: ... break; default: widget_state = WIDGET_INVALID_STATE; /* Fall through */ case WIDGET_INVALID_STATE: widget_ready = 0; widget_hardware_off(); ... do whatever else is necessary to establish a "safe" condition }

然后,其他地方的代码可以检查(widget_state == WIDGET_INVALID_STATE)并提供任何错误报告或状态重置行为.例如,状态栏代码可能会显示错误图标,并且可以为WIDGET_INVALID_STATE和WIDGET_IDLE启用在大多数非空闲状态下禁用的"启动窗口小部件"菜单选项.


Bre*_*ent 6

另一个示例:如果"default"是一个意外情况,并且您想记录错误但也做一些合理的事情,这可能很有用.我自己的一些代码示例:

  switch (style)
  {
  default:
    MSPUB_DEBUG_MSG(("Couldn't match dash style, using solid line.\n"));
  case SOLID:
    return Dash(0, RECT_DOT);
  case DASH_SYS:
  {
    Dash ret(shapeLineWidth, dotStyle);
    ret.m_dots.push_back(Dot(1, 3 * shapeLineWidth));
    return ret;
  }
  // more cases follow
  }
Run Code Online (Sandbox Code Playgroud)


Pre*_*vic 5

在某些情况下,您要将ENUM转换为字符串或将字符串转换为枚举,以防您正在写入/读取文件.

您有时需要使其中一个值默认为覆盖手动编辑文件所产生的错误.

switch(textureMode)
{
case ModeTiled:
default:
    // write to a file "tiled"
    break;

case ModeStretched:
    // write to a file "stretched"
    break;
}
Run Code Online (Sandbox Code Playgroud)