【发布时间】:2016-07-01 16:19:30
【问题描述】:
很抱歉这个问题的标题含糊不清,但我不知道如何准确地问这个问题。
以下代码在 Arduino 微处理器(为 ATMega328 微处理器编译的 c++)上执行时可以正常工作。返回值在代码中以 cmets 显示:
// 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”(好)、“Found at i = 2”(好)、“-999”(坏!)。
因此,尽管在 return 2; 行之前执行了 print 语句,并且从未在 return -999; 行之前执行了 print 语句,但该函数仍返回 -999。
有人可以帮助我了解这里发生了什么吗?我知道 c 中的函数内部的变量理论上可以包含任何旧垃圾,除非它们被初始化,但在这里我专门检查了一个打印语句,这还没有发生,但是......
编辑:感谢所有参与的人,特别是 underscore_d 的出色回答。似乎未定义的行为确实导致编译器跳过任何涉及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,这就是代码似乎可以工作并且打印语句按预期输出的原因(例如,第 122 行,“i”被递增)。
然而,当涉及到返回这个伪变量时,这对于我们久经考验的编译器来说太过分了;它画线,并将我们转储到另一个 return 语句(第 120 行跳转到第 132 行,将“-999”加载到寄存器 24 和 25,然后返回到main())。
或者至少,这是我对装配的有限掌握所能做到的。故事的寓意是,当您的代码行为未定义时,就会发生奇怪的事情。
【问题讨论】:
-
一个函数只有一个返回点是一种很好的做法。我建议重构代码。同样正如其他人指出的那样,永远不要使用未初始化的变量。如果您确定它会在使用之前在其他地方初始化,则不必在声明的位置对其进行初始化。
-
@RealtimeRik 我不认为这是一个普遍接受的观点。我发现提前返回代码比人工拉伸到单个返回点的代码更具可读性。
-
@RealtimeRik 请务必将我们链接到参考资料,或者至少总结一下您看到多个
return点被使用的常见情况以及您将采取的措施。 “良好实践”的含义是它是普遍接受的经验法则,但我不记得阅读过任何与此相关的具体建议。 -
@CharlieB 绝对!我最终会把那台 Mega2560 从柜子里拿出来,当我这样做的时候……它会很光荣。另外,近年来我在 Z80 和 68000 领域进行了一些非常有趣的尝试,需要想另一个借口回去。 :D
-
@underscore_d 已上传完整版供您欣赏。