初始化int会影响函数返回值

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情况下):

https://justpaste.it/vwu8

如果仔细观察,编译器似乎将寄存器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:你只需要修复你的代码以便它确定性地运行.

你为什么要使用它未经初始化?这只是'学术'吗?

  • @CharlieB看到我的编辑.UB _precisely_解释了这里发生了什么. (2认同)
  • @CharlieB它*没有*'解析'UB - UB意味着代码的执行没有行为保证.如果你想要更精确地回答究竟发生了什么,请发布已编译代码的反汇编:) (2认同)