C switch语句中的意外(和未处理)值

时间:2014-04-30 14:22:08

标签: c switch-statement

鉴于此代码:

typedef enum {
    Test0 = 0,
    Test1 = 1,
    Test3 = 3
} TestEnum;

如果发生这种情况会怎样?

TestEnum a = 2;

switch(a) {
    case Test0: println("0"); break;
    case Test1: println("1"); break;
    case Test3: println("3"); break;
}

我正试图在我的交换机中没有default的情况,所以编译器会告诉我什么时候我没有处理我的枚举中的任何新值(枚举变化相当频繁并由一个提供外部图书馆)。

处理我的垃圾方法的最佳方法是什么?我可以让交换机处理该值还是会产生意想不到的后果?


很多评论/回答说没有任何反应,因为它没有处理这是(a)好消息,但(b)有点违背了编译器如何实现切换。

我了解到编译器不只是逐个查看每个选项,而是使用函数指针的查找表来实现切换。这样我们就没有其他 - 如果每个case,我们就会到达匹配的那个。

鉴于上面的开关,编译器将创建3个看起来像这样的函数。 。

address     code

    1       a = 2
    2       locations = [100, 200, 0, 300] // Compiler makes this lookup for us
    3       goto locations[a] // This single line is our switch :)
    4       carry on with the rest of the app 

  100       println("1")
  101       goto 4
  200       println("2")
  201       goto 4
  300       println("3")
  301       goto 4

好的,所以我的简化伪代码很糟糕,但是你明白了 - 这种查找方法允许C开关为1条指令,无论选项的数量如何。

所以,我的问题是 - 如果您传入的值不在该查找数组(locations)中会发生什么。我可以理解locations[2]0所以它在运行时知道不会去那里,但如果我传入4 - 或-1该怎么办?

2 个答案:

答案 0 :(得分:3)

开关与任何情况都不匹配。它不会进入主体。

解决您的编辑问题。你确定你对开关编译的想法吗?我用GCC和-02

编译了这个函数
    typedef enum {
        Test0 = 0,
        Test1 = 1,
        Test3 = 3
    } TestEnum;

    void myfunc(TestEnum a)
    {

    switch(a) {
        case Test0: println("0"); break;
        case Test1: println("1"); break;
        case Test3: println("3"); break;
    }

    return;
    }

汇编是这样的:

    .file   "myfunc.c"
    .section    .rodata.str1.1,"aMS",@progbits,1
.LC0:
    .string "0"
.LC1:
    .string "1"
.LC2:
    .string "3"
    .text
    .p2align 4,,15
    .globl  myfunc
    .type   myfunc, @function
myfunc:
.LFB0:
    .cfi_startproc
    cmpl    $1, %edi
    je  .L3
    jb  .L4
    cmpl    $3, %edi
    jne .L8
    movl    $.LC2, %edi
    xorl    %eax, %eax
    jmp println
    .p2align 4,,10
    .p2align 3
.L8:
    rep ret
    .p2align 4,,10
    .p2align 3
.L4:
    movl    $.LC0, %edi
    xorl    %eax, %eax
    jmp println
    .p2align 4,,10
    .p2align 3
.L3:
    movl    $.LC1, %edi
    xorl    %eax, %eax
    jmp println
    .cfi_endproc
.LFE0:
    .size   myfunc, .-myfunc
    .ident  "GCC: (GNU) 4.8.2 20140206 (prerelease)"
    .section    .note.GNU-stack,"",@progbits

你可以看到它正在进行比较和跳跃。没有你怀疑的间接分支。

答案 1 :(得分:0)

switch语句是一种计算goto。每个case语句都是执行可以跳转的标签。您可以将评估视为(1)评估开关中的表达式以计算值,(2)将计算值与每个case语句进行比较,(3)如果找到匹配则执行跳转到那个案例陈述。

break语句的原因是提供从单个case部分到switch体末端的跳转。如果没有break语句或return语句或其他类型的跳转,执行将继续执行以下case语句。

默认情况下,您可以捕获那些个别情况与开关计算值不匹配的时间。如果您没有提供默认大小写,那么在交换机主体的末尾会添加一种隐藏的默认值作为占位符。换句话说,如果没有默认情况且没有case语句值与开关的计算值匹配,则跳过整个开关体并在闭合括号后继续执行。

默认情况的最常见用法是在没有任何案例匹配时提供默认操作。但是,默认的另一个典型用法是在切换计算值与任何case语句不匹配时捕获,以便生成ASSERT或日志。

对于switch()的有趣用法,请参阅Duff's device for serial copy使用带有连字符的案例陈述。

在C中,枚举作为一种类型比在C ++中更松散地处理,并且基本上只是int的符号,语法糖,尽管用于表示枚举值的基础类型可能是int或更小,具体取决于编译器实现。有关C enum的基本特征,请参阅C Enumeration Specifiers