Инициализация int влияет на возвращаемое значение функции
Извините за неопределенность названия этого вопроса, но я не уверен, как именно это задать.
Следующий код, выполняемый на микропроцессоре Arduino (с ++ скомпилирован для микропроцессора ATMega328), работает нормально. Возвращаемые значения отображаются в комментариях в коде:
// 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» (плохо!).
Таким образом, функция возвращает -999, несмотря на то, что выполнила инструкцию print непосредственно передreturn 2;
и несмотря на то, что оператор print никогда не выполнялся непосредственно передreturn -999;
линия.
Может ли кто-нибудь помочь мне понять, что здесь происходит? Я понимаю, что переменные внутри функций в c теоретически могут содержать любой старый мусор, если они не инициализированы, но здесь я специально проверяю в выражении print, что этого не произошло, и пока ...
РЕДАКТИРОВАТЬ: Спасибо всем, кто принял участие, и особенно underscore_d за их отличный ответ. Кажется, что неопределенное поведение действительно приводит к тому, что компилятор просто пропускает все, что связано сi
, Вот некоторые из сборок с serial.prints в Detection Semicolon закомментированы:
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
, Этот регистр обрабатывается так, как если бы он содержалi
в циклах while, в операторах if и т. д., поэтому кажется, что код работает, и операторы print выводятся, как и ожидалось (например, строка 122, где «i» увеличивается).
Однако, когда дело доходит до возврата этой псевдопеременной, это слишком далеко для нашего проверенного и испытанного компилятора; он рисует линию и выводит нас в другой оператор возврата (строка 120 переходит на строку 132, загружая «-999» в регистры 24 и 25, прежде чем вернуться кmain()
).
Или, по крайней мере, это насколько я могу получить с моим ограниченным пониманием сборки. Мораль этой истории - странные вещи, которые происходят, когда поведение вашего кода не определено.