很抱歉这个问题的标题含糊不清,但我不确定如何确切地问这个问题。
以下代码在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"
}
按预期输出“2”作为第一个分号的位置。
但是,如果我将detectSemicolon
函数的第一行更改为int i;
,即没有显式初始化,我就会遇到问题。具体来说,输出是“i = 0”(好),“发现在i = 2”(好),“ - 999”(坏!)。
因此,尽管在return 2;
行之前执行了print语句,并且尽管从未在return -999;
行之前执行print语句,但函数仍返回-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>
看起来编译器实际上完全忽略了while循环,并断定输出将始终为“-999”,因此它甚至不会打扰函数调用,而是硬编码0xFC19。我将再次启用serial.prints,以便仍然可以调用该函数,但我认为这是一个强大的指针。
编辑2:
对于那些真正关心的人,这里是完全如上所示的反汇编代码的链接(在UB案例中):
如果仔细观察,编译器似乎将寄存器28指定为i
的位置,并在行d8
中将其初始化为零。该寄存器被视为在while循环,if语句等中包含i
,这就是代码看起来有效并且print语句按预期输出的原因(例如,行“122”,其中“i”增加)。
然而,当涉及到返回这个伪变量时,对于我们久经考验的编译器来说,这是一个太过分了;它绘制线,并将我们转储到另一个return语句(第120行跳转到第132行,在返回main()
之前将“-999”加载到寄存器24和25中)。
或者至少,就我对集会的有限把握而言,这是我所能得到的。当你的代码的行为未定义时,故事的道德是奇怪的事情发生。
答案 0 :(得分:10)
与所有基本类型的非static
存储持续时间一样,声明但未定义int
不会导致默认初始化。它使变量未初始化。那个不意味着i
只保留一个随机值。它包含 no (已知,有效)值,因此您不允许读取它。
这是C ++ 11标准中的相关引用,在评论中通过 Angew 。这不是一个新的限制,从那时起它也没有改变:
C ++ 11 4.1 / 1,讨论左值到右值的转换(基本上是读取变量的值):“如果glvalue引用的对象是......未初始化,则需要进行此转换的程序具有未定义的行为。“
任何读取的unitialised变量都会导致未定义的行为,因此任何都可能发生。使用某些未知的默认值而不是你的程序继续按预期运行,编译器可以使它完全做任何事情,因为行为是未定义的,并且标准对这种情况下应该发生的事情没有要求
实际上,这通常意味着优化编译器可能只是删除任何依赖于UB的代码。没有办法正确地决定做什么,所以决定什么也不做是完全有效的(这也恰好是对尺寸和速度的优化)。或者正如评论者提到的那样,它可能会保留代码,但会替换尝试读取i
与最接近的无关值,或者使用不同语句中的不同常量等等。
按照您的想法打印变量并不算作“检查”,因此没有区别。没有办法'检查'未初始化的变量,从而接种自己对抗UB。只有在程序已经为其编写了特定值时,才会定义读取变量的行为。
我们没有必要推测为什么会出现特定的任意类型的UB:你只需要修复你的代码,使其确定性地运作。
为什么你要使用它未经初始化?这只是'学术'吗?
答案 1 :(得分:0)
当你没有初始化变量时,它有一个随机值,无论内存地址是什么,所以while (i <=strlen(str))
将会出现不可预测的行为。
你应该始终初始化。
(Visual Studio Debug配置自动初始化变量。)