Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/142.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 开关情况下的变量:UB还是编译器错误?_C++_Gcc_Arduino_Switch Statement_Language Lawyer - Fatal编程技术网

C++ 开关情况下的变量:UB还是编译器错误?

C++ 开关情况下的变量:UB还是编译器错误?,c++,gcc,arduino,switch-statement,language-lawyer,C++,Gcc,Arduino,Switch Statement,Language Lawyer,我正在尝试确定是调用了未定义的行为还是遇到了编译器错误 我正在开发一些代码来解释通过串行连接从外部组件发送到Arduino的消息。这是我开始使用的成员函数的简化版本。[串行.println命令是与printf调试等效的Arduino命令。] void decodeMessage() { switch (getType()) { case 0x3A: Serial.println("foo message"); break; case 0x3B:

我正在尝试确定是调用了未定义的行为还是遇到了编译器错误

我正在开发一些代码来解释通过串行连接从外部组件发送到Arduino的消息。这是我开始使用的成员函数的简化版本。[串行.println命令是与printf调试等效的Arduino命令。]

void decodeMessage() {
  switch (getType()) {
    case 0x3A:
      Serial.println("foo message");
      break;
    case 0x3B:
      Serial.println("bar message");
      break;
    case 0x3C:
      Serial.println("zerz message");
      break;
    ... // and so on for 0x3D through 0x40
    case 0x41:
      Serial.println("ack message");
      break;
    default:
      Serial.println("unknown message type");
      break;
  }
}
这对所有消息类型都很有效。 然后,我修改了0x3B的大小写,以检查消息参数中的一些位:

    case 0x3B:
      Serial.println("bar message");
      const auto mask = getParam();
      if (mask & 0x01) Serial.println("bit 0 set");
      if (mask & 0x02) Serial.println("bit 1 set");
      break;
使用此代码替换原始0x3B情况下,除了最后一个消息类型(0x41,“ack”)之外,其他一切都正常工作。好像那个案子的尸体不见了。默认情况继续工作,0x3A到0x40也是如此

尝试了找出问题的原因之后,我意识到我在开关的中间引入了一个const变量(<代码>掩码< /代码>),而没有将它限制在那个特定的情况下。当我添加大括号时,它再次适用于所有情况:

    case 0x3B: {
      Serial.println("bar message");
      const auto mask = getParam();
      if (mask & 0x01) Serial.println("bit 0 set");
      if (mask & 0x02) Serial.println("bit 1 set");
      break;
    }  // braces to limit scope of `mask`
问题:

  • 损坏的版本是否调用了未定义的行为,或者这是一个编译器错误?如果是UB,我应该重新阅读规范的哪一部分

  • 我使用过的其他编译器(例如,VC++)在开关盒中引入变量而不限制其范围时会发出警告。有没有一个选项可以从gcc(ArduinoIDE使用的编译器)获得类似这样的警告


我认为此代码的格式不正确,原因如下:

可以传输到块中,但不能以绕过初始化声明(包括条件和init语句中的声明)的方式传输从具有自动存储持续时间的变量不在范围内的点跳到它在范围内的点的程序是格式错误的,除非该变量具有真空初始化([dcl.init])

我的。您的变量
掩码
没有。至少就我的理解而言,“格式错误”隐含地要求进行诊断,即,符合标准的编译器必须生成错误消息。因此,没有未定义的行为,这应该永远不会编译

因此,我想说,这里缺少诊断肯定会被视为编译器错误。但是,请注意,可以在godbolt上尝试的GCC版本中没有一个接受此代码(它们可以追溯到很久以前)。如果Arduino IDE真的毫不犹豫地编译了这段代码,那么它一定是在使用一个毫无希望的过时/损坏的GCC版本

要解决这个问题,只需将变量包装在块作用域中,这样就没有可能的控制流可以在不传递声明的情况下进入声明变量的作用域,正如您已经发现的那样。例如,打开这个

void f(int x)
{
    switch (x)
    {
    case 1:
        const int y = 42;  // error
        break;

    case 2:
        break;
    }
}
进入


您应该添加可能的fixGood标准参考。我认为Arduino使用了一个名为avr gcc的自定义gcc版本,但我不知道这方面的详细信息。这似乎是一个古老的gcc的叉子。正如我在问题中所展示的那样,我已经理解了修复方法。我只是想弄明白它是如何在没有任何警告的情况下导致这种特别奇怪的行为的。@AdrianMcCarthy我最初没有添加修复程序,因为在我看来,你并不是在要求修复(你自己已经发现了解决方案)。不管怎样,我现在加上它,以防将来有人会觉得它有用。关于GCC,你是在编译C代码而不是C++吗?这里似乎有一个潜在的相关bug:……这个bug肯定在大致范围内。但是代码确实被编译为C++,因为所讨论的函数是成员函数。
void f(int x)
{
    switch (x)
    {
    case 1:
        {
            const int y = 42;  // OK
            break;
        }

    case 2:
        break;
    }
}