【问题标题】:Can I put code outside of cases in a switch?我可以将代码放在开关中的案例之外吗?
【发布时间】:2015-03-13 01:54:43
【问题描述】:

假设:

switch ( test ) {

  // Set some variables, call some functions ?
  int x = 1 ;
  int y = function(x) ;
  //

  case 1 : 
    // Process for test = 1
    ...
    break;

  case 5 : 
    // Process for test = 5
    ...
    break;

  default : 
    // Process for all other cases.
    ...

}

执行我在第一个 case 之前添加的那段额外代码是否“合法”?我从未在示例中看到过这种情况。

【问题讨论】:

  • 如果你尝试编译它会发生什么?
  • 可以,但不推荐。
  • 你为什么不想把你的代码放在开关之前?
  • C++ 不允许这样的跳转。 C原谅但不知道是否初始化。
  • @lurker,它使用 Borland C++ Builder 2009 编译,这让我感到惊讶,因此我提出了问题。这是一个简单的解决方法(将代码放在 switch() 之前),但出于好奇,我尝试了它,而且它编译的仅仅是事实就提出了问题。

标签: c++ c


【解决方案1】:

首先了解switch(真的)如何工作的背景:

switch 通常被认为是一个结构,它根据某个表达式的值选择一段代码来执行,如

switch (x) {
case 1:
    foo();
    break;

case 2:
    bar();
    break;
}

但是,将switch 视为计算goto 语句的一种形式更为准确。例如,以下内容是完全合法的:

switch (x) {
    puts("I can't be reached");
case 1:
    if (cond) {
case 2:
        puts("Either x == 1 && cond, or x == 2");
    }
}

根据x 的值,程序将跳转到case 1case 2(如果x 既不是1 也不是2,则跳转到switch)。


您的程序将编译为 C(在 switch 中带有垃圾值 xy,因为跳过了初始化),但不是 C++。原因是 C++ 不允许跳转到 case 标签来跨越变量的初始化。对于像int 这样的简单类型,允许跳过int x;(因为不涉及初始化),但不能跳过int x = 1;

造成这种差异的主要动机可能是,当涉及构造函数时,让跳转到 case 标签跨越初始化在 C++ 中是不安全的。例如,如果 C++ 允许在某个范围内的定义 My_class my_object 之后出现 case 标签,那么跳转到该 case 标签将跳过 my_object 的构造函数,但在退出该范围时仍会运行其析构函数。

相同的限制适用于 C++ 中的 goto。您不能使用它来跳转到一个块并过去一个变量初始化。


附带说明,switch 遵循与ifwhile 相同的一般语法。 C11 标准(ISO/IEC 9899:2011,第 6.8.4 节)中给出的if 的语法是

if(表达式)语句

,而switch的语法是

switch(表达式)语句

statement 而言,唯一的区别(在 C 中——C++ 增加了一些如上所述的限制)是允许包含case 标签(和break)用于switch,但不适用于if(除非if 出现在switch 中)。

就像if 一样,您甚至可以去掉大括号并编写如下代码。 (这是否造成不必要的混乱是另一个讨论。)

switch (x) case 1: case 2: puts("x is 1 or 2");

在语法上,casedefault 标签与 goto 标签属于同一类别。 C11标准第6.8.1节有如下定义:

标签声明:
标识符 : 声明
case 常量表达式 : 语句
默认 声明

【讨论】:

  • 我真诚地希望我永远不会遇到像结尾那样的 switch 语句。
  • @Qix:可能会作为一种策略来使您的代码足够神秘,以至于您不会被解雇。 :)
  • 也许吧,但是你必须面对你的老板怨恨你,同事把东西放在你的咖啡里......这不是一个好情况。
【解决方案2】:

您可以通过一个简单的测试了解发生了什么:

int w = 1;

switch (w)
{
    int i = 3;
    int y = foo(i);

case 1:
    printf("here %d\n", y);
    printf("here %d\n", i);
    break;

case 2:
    printf("not here\n");
    break;
}

此代码将在使用gcc 的函数内部编译。 C 编译器看到 iy 在由大括号分隔的块内声明,因此将接受它。但是,printf 语句将打印 iy 的垃圾,因为从未执行分配。这是因为switch 语句形成到case 的跳转,该跳转对应于switch 头部的表达式。因此无法访问左大括号和第一个 case 之间的可执行代码。请参阅Why can't variables be declared in a switch statement?,它没有解释完全相同的场景,但确实对switch 语句进行了一些相关讨论。

如果你在打开警告 (gcc -Wall) 的情况下编译它,你会得到:

foo.c: In function ‘main’:
foo.c:19:15: warning: ‘y’ may be used uninitialized in this function [-Wuninitialized]
foo.c:20:15: warning: ‘i’ may be used uninitialized in this function [-Wuninitialized]

有趣的是,以下代码将在没有警告的情况下编译并正常工作:

int w = 1;

switch (w)
{
    int i;
    int y;

case 1:
    i = 2; y = 3 * i;
    printf("here %d\n", y);
    printf("here %d\n", i);
    break;

case 2:
    i = 1; y = 2;
    printf("here %d\n", y);
    printf("here %d\n", i);
    break;
}

变量按您期望的方式打印,因为它们是在 switch 块的范围内声明的,并且在执行发生的 case 部分中设置的值。它在这种情况下有效的事实并不是说它是推荐的做法。 :)

【讨论】:

    【解决方案3】:

    控制可以到达switch头部和第一个case表达式之间的语句——但是一些其他的控制结构必须将它发送到那里。例如,没有什么可以阻止您访问interlacing a switch and a loop

    #include <stdio.h>
    
    int main(void)
    {
        int x = 1;
        switch (x) {
            do {
                printf("got here with x=%d\n", x);
        case 2:
                puts("case two or loop");
        case 1:
                puts("case one or loop");
            } while (++x < 3);
        }
        return 0;
    }
    

    -std=c11 模式下编译时没有来自 gcc 4.9 或 clang 3.5 的任何投诉,警告会尽可能高,运行时会打印:

    case one or loop
    got here with x=2
    case two or loop
    case one or loop
    

    有趣的事实:启用优化后,两个编译器实际上都会生成 100% 的直线代码:

    main:
        subq    $8, %rsp
        movl    $.LC0, %edi
        call    puts
        movl    $2, %esi
        movl    $.LC1, %edi
        xorl    %eax, %eax
        call    printf
        movl    $.LC2, %edi
        call    puts
        movl    $.LC0, %edi
        call    puts
        xorl    %eax, %eax
        addq    $8, %rsp
        ret
    

    【讨论】:

      【解决方案4】:

      这是不合法的,当你编译时你会得到一堆“crosses initialization of ...”错误。这里有一个很好的解释: Getting a bunch of crosses initialization error

      基本上,switch case 的工作原理是跳过 case 语句之间的任何代码,跳过变量的初始化是非法的。如果您只是在两者之间放置一个函数调用,它在技术上是合法的,并且可以编译,尽管该函数实际上永远不会被调用。

      int test = 1;
      int x = 1 ; 
      switch ( test ) { 
      
          //Technically legal, although will never be called.
          std::cout<<somefunc(x)<<std::endl;;  
      
          //jumps straight here, over any code above it
          case 1 : 
              std::cout<<"1"<<std::endl;
              break;
      
          case 5 : 
              std::cout<<"5"<<std::endl;
              break;
      
          default : 
              std::cout<<"Default."<<std::endl;
      }  
      

      【讨论】:

      • 这在 C 中是合法的(但这是一个非常糟糕的主意)。在 C++ 中是非法的。
      • 它是使用 Borland C++ Builder 2009 编译的,但它确实只是一个函数调用(我没有检查它是否被调用),我在没有测试的情况下扩展了这个问题的更多可能性。我基本上认为它是非法的,所以我很惊讶它编译了,而且由于我不信任它,我也不能相信发生的任何事情。可以向专家提问。
      【解决方案5】:

      我试图编译一个类似于你问的代码,它至少出现一个错误:

      错误 C2360:'x' 的初始化被 'case' 标签跳过

      你多余的代码从来没有被执行过,因为在switch检查语句'test'之后,它会转到相应的标签,你多余的代码不在任何标签下,因此它永远被跳过。

      为什么不能在 switch 语句之前添加额外的代码?有什么原因吗?

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-12-08
        • 1970-01-01
        • 1970-01-01
        • 2013-03-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多