Cha*_*ieB 4 c++ embedded arduino
很抱歉这个问题的标题含糊不清,但我不确定如何确切地问这个问题.
以下代码在Arduino微处理器上执行时(为ATMega328微处理器编译的c ++)工作正常.返回值显示在代码中的注释中:
// Return the index of the first semicolon in a string
int detectSemicolon(const char* str) {
int i = 0;
Serial.print("i = ");
Serial.println(i); // prints "i = 0"
while (i <= strlen(str)) {
if (str[i] == ';') {
Serial.print("Found at i = ");
Serial.println(i); // prints "Found at i = 2"
return i;
}
i++;
}
Serial.println("Error"); // Does not execute
return -999;
}
void main() {
Serial.begin(250000);
Serial.println(detectSemicolon("TE;ST")); // Prints "2"
}
Run Code Online (Sandbox Code Playgroud)
如预期的那样,它输出"2"作为第一个分号的位置.
但是,如果我将detectSemicolon函数的第一行更改为int i;即没有显式初始化,我就会遇到问题.具体来说,输出是"i = 0"(好),"发现在i = 2"(好)," - 999"(坏!).
因此该函数返回-999,尽管return 2;在行之前执行了print语句,并且尽管从未在行之前执行print语句return -999;.
有人能帮我理解这里发生了什么吗?我理解c中函数内部的变量理论上可以包含任何旧垃圾,除非它们已经初始化,但是在这里我特意检查了一个打印声明,但这还没有发生,但是......
编辑:感谢所有参与其中的人,特别是对于他们的优秀答案.似乎未定义的行为确实导致编译器跳过任何涉及的内容i.以下是detectSemicolon中带有serial.prints的一些程序集注释掉:
void setup() {
Serial.begin(250000);
Serial.println(detectSemicolon("TE;ST")); // Prints "2"
d0: 4a e0 ldi r20, 0x0A ; 10
d2: 50 e0 ldi r21, 0x00 ; 0
d4: 69 e1 ldi r22, 0x19 ; 25
d6: 7c ef ldi r23, 0xFC ; 252
d8: 82 e2 ldi r24, 0x22 ; 34
da: 91 e0 ldi r25, 0x01 ; 1
dc: 0c 94 3d 03 jmp 0x67a ; 0x67a <_ZN5Print7printlnEii>
Run Code Online (Sandbox Code Playgroud)
看起来编译器实际上完全忽略了while循环并得出结论输出将始终为"-999",因此它甚至不打扰调用函数,而是硬编码0xFC19.我将再次启用serial.prints,以便仍然可以调用该函数,但我认为这是一个强大的指针.
编辑2:
对于那些真正关心的人,这里是完全如上所示的反汇编代码的链接(在UB情况下):
如果仔细观察,编译器似乎将寄存器28指定为i并将其"初始化"为零的位置d8.该寄存器被视为包含i整个while循环,if语句等,这就是代码看起来有效并且print语句按预期输出的原因(例如,行"122",其中"i"增加).
但是,当涉及到返回这个伪变量时,对于我们久经考验的编译器来说,这是一个太过分了; 它绘制线,并将我们转储到另一个return语句(第120行跳转到第132行,在返回之前将"-999"加载到寄存器24和25中main()).
或者至少,就我对集会的有限把握而言,这是我所能得到的.当你的代码的行为未定义时,故事的道德是奇怪的事情发生.
und*_*e_d 10
与所有基本类型的非static存储持续时间一样,声明但不定义int不会导致默认初始化.它使变量未初始化.但这并不意味着i只是拥有一个随机值.它没有(已知的,有效的)值,因此您不允许读它.
以下是C++ 11标准中的相关引用,通过Angew的评论.这不是一个新的限制,从那时起它也没有改变:
C++ 11 4.1/1,讨论左值到右值的转换(基本上是读取变量的值):"如果glvalue引用的对象是......未初始化,则需要进行此转换的程序具有未定义的行为. "
任何读取整数变量都会导致未定义的行为,因此任何事情都可能发生.编译器可以使其完成任何操作,而不是您的程序继续按预期使用某些未知的默认值,因为行为未定义,并且标准对此类场景中应该发生的事情没有要求.
实际上,这通常意味着优化编译器可能只是删除任何依赖于UB的代码.没有办法正确地决定做什么,所以决定什么都不做是完全有效的(这也恰好是对尺寸和速度的优化).或者正如评论者提到的那样,它可能会保留代码,但会替换尝试i使用最接近的无关值进行读取,或者使用不同语句中的不同常量等等.
打印变量不会像你想象的那样计算'检查',所以没有区别.没有办法'检查'未初始化的变量,从而接种自己对抗UB.只有在程序已经为其编写了特定值时,才会定义读取变量的行为.
我们没有必要推测为什么会发生特定的任意类型的UB:你只需要修复你的代码以便它确定性地运行.
你为什么要使用它未经初始化?这只是'学术'吗?