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

时间:2016-07-01 16:19:30

标签: c++ arduino embedded

很抱歉这个问题的标题含糊不清,但我不确定如何确切地问这个问题。

以下代码在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案例中):

https://justpaste.it/vwu8

如果仔细观察,编译器似乎将寄存器28指定为i的位置,并在行d8中将其初始化为零。该寄存器被视为在while循环,if语句等中包含i,这就是代码看起来有效并且print语句按预期输出的原因(例如,行“122”,其中“i”增加)。

然而,当涉及到返回这个伪变量时,对于我们久经考验的编译器来说,这是一个太过分了;它绘制线,并将我们转储到另一个return语句(第120行跳转到第132行,在返回main()之前将“-999”加载到寄存器24和25中)。

或者至少,就我对集会的有限把握而言,这是我所能得到的。当你的代码的行为未定义时,故事的道德是奇怪的事情发生。

2 个答案:

答案 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配置自动初始化变量。)