C语言 switch 语句:必须默认为最后一种情况吗?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3110088/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 05:43:43  来源:igfitidea点击:

Switch statement: must default be the last case?

cswitch-statement

提问by tanascius

Consider the following switchstatement:

考虑以下switch语句:

switch( value )
{
  case 1:
    return 1;
  default:
    value++;
    // fall-through
  case 2:
    return value * 2;
}

This code compiles, but is it valid (= defined behavior) for C90/C99? I have never seen code where the defaultcase is not the last case.

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

EDIT:
As Jon Cageand KillianDSwrite: this is really ugly and confusing code and I am well aware of it. I am just interested in the general syntax (is it defined?) and the expected output.

编辑:
正如Jon CageKillianDS所写:这是非常丑陋且令人困惑的代码,我很清楚这一点。我只对通用语法(是否已定义?)和预期输出感兴趣。

采纳答案by Secure

The C99 standard is not explicit about this, but taking all facts together, it is perfectly valid.

C99 标准没有明确说明这一点,但综合所有事实,它是完全有效的。

A caseand defaultlabel are equivalent to a gotolabel. See 6.8.1 Labeled statements. Especially interesting is 6.8.1.4, which enables the already mentioned Duff's Device:

Acasedefaultlabel 等价于一个goto标签。见 6.8.1 带标签的语句。特别有趣的是 6.8.1.4,它启用了已经提到的 Duff's Device:

Any statement may be preceded by a prefix that declares an identifier as a label name. Labels in themselves do not alter the flow of control, which continues unimpeded across them.

任何语句前面都可以有一个前缀,该前缀将标识符声明为标签名称。标签本身不会改变控制流,控制流在它们之间继续畅通无阻。

Edit: The code within a switch is nothing special; it is a normal block of code as in an if-statement, with additional jump labels. This explains the fall-through behaviour and why breakis necessary.

编辑:开关中的代码没什么特别的;它是一个普通的代码块,就像在 -if语句中一样,带有额外的跳转标签。这解释了失败行为以及为什么break是必要的。

6.8.4.2.7 even gives an example:

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); 
} 

In the artificial program fragment the object whose identifier is i exists with automatic storage duration (within the block) but is never initialized, and thus if the controlling expression has a nonzero value, the call to the printf function will access an indeterminate value. Similarly, the call to the function f cannot be reached.

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

The case constants must be unique within a switch statement:

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

6.8.4.2.3 The expression of each case label shall be an integer constant expression and no two of the case constant expressions in the same switch statement shall have the same value after conversion. There may be at most one default label in a switch statement.

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

All cases are evaluated, then it jumps to the default label, if given:

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

6.8.4.2.5 The integer promotions are performed on the controlling expression. The constant expression in each case label is converted to the promoted type of the controlling expression. If a converted value matches that of the promoted controlling expression, control jumps to the statement following the matched case label. Otherwise, if there is a default label, control jumps to the labeled statement. If no converted case constant expression matches and there is no default label, no part of the switch body is executed.

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

回答by Salil

The case statements and the default statement can occur in any order in the switch statement. The default clause is an optional clause that is matched if none of the constants in the case statements can be matched.

case 语句和 default 语句可以在 switch 语句中以任意顺序出现。default 子句是一个可选子句,如果 case 语句中的任何常量都无法匹配,则匹配该子句。

Good Example :-

好的例子 :-

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


Outputs '2,default'

very useful if you want your cases to be presented in a logical order in the code (as in, not saying case 1, case 3, case 2/default) and your cases are very long so you do not want to repeat the entire case code at the bottom for the default

如果您希望您的案例在代码中以逻辑顺序呈现(例如,不是说案例 1、案例 3、案例 2/默认)并且您的案例很长,因此您不想重复整个案例,则非常有用默认底部的代码

回答by kriss

It's valid and very useful in some cases.

它在某些情况下有效且非常有用。

Consider the following code:

考虑以下代码:

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
}

The point is that the above code is more readable and efficient than cascaded if. You could put defaultat the end, but it is pointless as it will focus your attention on error cases instead of normal cases (which here is the defaultcase).

重点是上面的代码比级联更可读和更高效if。你可以把default它放在最后,但它毫无意义,因为它会将你的注意力集中在错误情况而不是正常情况(这里就是这种default情况)。

Actually, it's not such a good example, in pollyou know how many events may occur at most. My real point is that there arecases with a defined set of input values where there are 'exceptions' and normal cases. If it's better to put exceptions or normal cases at front is a matter of choice.

实际上,这不是一个很好的例子,因为poll您知道最多可能发生多少个事件。我真正的问题是,有有定义的一组那里有“例外”和正常情况下的输入值的情况。如果最好将异常或正常情况放在前面是一个选择问题。

In software field I think of another very usual case: recursions with some terminal values.If you can express it using a switch, defaultwill be the usual value that contains recursive call and distinguished elements (individual cases) the terminal values. There is usually no need to focus on terminal values.

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

Another reason is that the order of the cases may change the compiled code behavior, and that matters for performances.Most compilers will generate compiled assembly code in the same order as the code appears in the switch. That makes the first case very different from the others: all cases except the first one will involve a jump and that will empty processor pipelines. You may understand it like branch predictor defaulting to running the first appearing case in the switch. If a case if much more common that the others then you have very good reasons to put it as the first case.

另一个原因是案例的顺序可能会改变编译后的代码行为,这对性能很重要。大多数编译器会按照代码出现在开关中的相同顺序生成编译后的汇编代码。这使得第一种情况与其他情况非常不同:除第一种情况外的所有情况都将涉及跳转,这将清空处理器管道。您可能会理解它就像分支预测器默认运行开关中第一个出现的情况一样。如果一个案例比其他案例更常见,那么您有充分的理由将其作为第一个案例。

Reading comments it's the specific reason why the original poster asked that question after reading Intel compiler Branch Loop reorganisationabout code optimisation.

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

Then it will become some arbitration between code readability and code performance. Probably better to put a comment to explain to future reader why a case appears first.

那么它将成为代码可读性和代码性能之间的某种仲裁。最好发表评论向未来的读者解释为什么首先出现一个案例。

回答by Jens Gustedt

yes, this is valid, and under some circumstances it is even useful. Generally, if you don't need it, don't do it.

是的,这是有效的,在某些情况下它甚至是有用的。一般来说,如果你不需要它,就不要这样做。

回答by Patrick Schlüter

There's no defined order in a switch statement. You may look at the cases as something like a named label, like a gotolabel. Contrary to what people seem to think here, in the case of value 2 the default label is not jumped to. To illustrate with a classical example, here is Duff's device, which is the poster child of the extremes of switch/casein C.

switch 语句中没有定义的顺序。您可以将案例视为命名标签之类的东西,例如goto标签。与人们在这里的想法相反,在值为 2 的情况下,默认标签不会跳转到。为了用一个经典的例子来说明,这里是Duff 的 device,它是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);
  }
}

回答by supercat

One scenario where I would consider it appropriate to have a 'default' located somewhere other than the end of a case statement is in a state machine where an invalid state should reset the machine and proceed as though it were the initial state. For example:

我认为将“默认”放置在 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;
}

an alternative arrangement, if an invalid state should not reset the machine but should be readily identifiable as an invalid state:

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

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 }

Code elsewhere may then check for (widget_state == WIDGET_INVALID_STATE) and provide whatever error-reporting or state-reset behavior seems appropriate. For example, the status-bar code could show an error icon, and the "start widget" menu option which is disabled in most non-idle states could be enabled for WIDGET_INVALID_STATE as well as WIDGET_IDLE.

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

回答by Brennan Vincent

Chiming in with another example: This can be useful if "default" is an unexpected case, and you want to log the error but also do something sensible. Example from some of my own code:

再举一个例子:如果“默认”是一个意外情况,这会很有用,并且你想记录错误但也做一些明智的事情。来自我自己的一些代码的示例:

  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
  }

回答by Predrag Manojlovic

There are cases when you are converting ENUM to a string or converting string to enum in case where you are writing/reading to/from a file.

在某些情况下,您正在将 ENUM 转换为字符串或将字符串转换为 enum,以防您正在向/从文件中写入/读取。

You sometimes need to make one of the values default to cover errors made by manually editing files.

有时您需要将其中一个值设为默认值以覆盖手动编辑文件所造成的错误。

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

case ModeStretched:
    // write to a file "stretched"
    break;
}

回答by Scott Thomson

The defaultcondition can be anyplace within the switch that a case clause can exist. It is not required to be the last clause. I have seen code that put the default as the first clause. The case 2:gets executed normally, even though the default clause is above it.

所述default条件可以是一个壳体子句可以存在于开关内的任何地方。它不需要是最后一个子句。我见过将默认值作为第一个子句的代码。在case 2:得到正常执行,即使违约条款是它上面。

As a test, I put the sample code in a function, called test(int value){}and ran:

作为测试,我将示例代码放在一个函数中,调用test(int value){}并运行:

  printf("0=%d\n", test(0));
  printf("1=%d\n", test(1));
  printf("2=%d\n", test(2));
  printf("3=%d\n", test(3));
  printf("4=%d\n", test(4));

The output is:

输出是:

0=2
1=1
2=4
3=8
4=10

回答by Jon Cage

It's valid, but rather nasty. I would suggest it's generally bad to allow fall-throughs as it can lead to some very messy spaghetti code.

这是有效的,但相当讨厌。我建议允许失败通常是不好的,因为它会导致一些非常混乱的意大利面条式代码。

It's almost certainly better to break these cases up into several switch statements or smaller functions.

几乎可以肯定,将这些情况分解为几个 switch 语句或更小的函数会更好。

[edit]@Tristopia: Your example:

[编辑]@Tristopia:你的例子:

Example from UCS-2 to UTF-8 conversion 

r is the destination array, 
wc is the input wchar_t  

switch(utf8_length) 
{ 
    /* Note: code falls through cases! */ 
    case 3: r[2] = 0x80 | (wc & 0x3f); wc >>= 6; wc |= 0x800; 
    case 2: r[1] = 0x80 | (wc & 0x3f); wc >>= 6; wc |= 0x0c0; 
    case 1: r[0] = wc;
}

would be clearer as to it's intention (I think) if it were written like this:

如果它是这样写的,它的意图(我认为)会更清楚:

if( utf8_length >= 1 )
{
    r[0] = wc;

    if( utf8_length >= 2 )
    {
        r[1] = 0x80 | (wc & 0x3f); wc >>= 6; wc |= 0x0c0; 

        if( utf8_length == 3 )
        {
            r[2] = 0x80 | (wc & 0x3f); wc >>= 6; wc |= 0x800; 
        }
    }
}   

[edit2]@Tristopia: Your second example is probably the cleanest example of a good use for follow-through:

[edit2]@Tristopia:您的第二个示例可能是用于后续操作的最干净的示例:

for(i=0; s[i]; i++)
{
    switch(s[i])
    {
    case '"': 
    case '\'': 
    case '\': 
        d[dlen++] = '\'; 
        /* fall through */ 
    default: 
        d[dlen++] = s[i]; 
    } 
}

..but personally I would split the comment recognition into it's own function:

..但我个人会将评论识别拆分为它自己的功能:

bool isComment(char charInQuestion)
{   
    bool charIsComment = false;
    switch(charInQuestion)
    {
    case '"': 
    case '\'': 
    case '\': 
        charIsComment = true; 
    default: 
        charIsComment = false; 
    } 
    return charIsComment;
}

for(i=0; s[i]; i++)
{
    if( isComment(s[i]) )
    {
        d[dlen++] = '\'; 
    }
    d[dlen++] = s[i]; 
}