我正在尝试确定是调用未定义行为还是遇到编译器错误。
我正在开发一些代码来解释通过串行连接从外部组件发送到Arduino的消息。这是我开始使用的成员函数的简化版本。 [Serial.println
命令相当于Arduino的printf调试。]
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变量(mask
),而没有将其限定在特定情况下。当我添加括号时,它再次适用于所有情况:
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(Arduino IDE使用的编译器)获得类似警告的选项?
答案 0 :(得分:7)
我认为此代码应该基于[stmt.dcl]/3格式错误:
可以转移到一个块中,但不能通过初始化(包括条件和init语句中的声明)绕过声明。 从具有自动存储持续时间的变量不在范围内的点跳转到其范围不正确的点的程序,除非该变量具有空虚的初始化([dcl.init])
强调我的。您的变量mask
没有vacuous initialization。至少就我的理解而言,“格式错误”隐式地需要诊断,即符合标准的编译器必须产生错误消息。因此,没有未定义的行为,这应该永远都不会编译。
因此,我想说的是,这里缺乏诊断程序绝对可以视为编译器错误。但是请注意,没有一个可以尝试使用Godbolt的GCC版本可以接受此代码(而且可以追溯到很久以前)。如果Arduino IDE确实确实编译了这段代码而没有退缩,那么看来Arduino IDE必须使用了一个绝望的过时/损坏的GCC版本。
example of proper compilers complaining about this
要解决此问题,只需将变量包装在块作用域中,这样就不会有可能的控制流进入已声明变量的作用域而无需通过变量声明,因为您已经发现了自己。例如,将其旋转
void f(int x)
{
switch (x)
{
case 1:
const int y = 42; // error
break;
case 2:
break;
}
}
进入
void f(int x)
{
switch (x)
{
case 1:
{
const int y = 42; // OK
break;
}
case 2:
break;
}
}