У меня есть следующий код C.
Теперь я знаю, что функции, имеющие тип возвращаемого значения int, будут иметь неявный тип возвращаемого значения, если нет оператора return. Я также понимаю, что чтение неявного значения является неопределенным поведением. Но я не могу понять это последовательное поведение: вывод этого кода — «hello5». Если я напечатаю «HelloWorld», то на выходе будет «HelloWorld10». Он возвращает тип возвращаемого значения функции printf?!
Я не очень понимаю как, может кто-нибудь объяснить?
#include <stdio.h>
int m()
{
printf("Hello");
}
void main() {
int k = m();
printf("%d",k);
}
И формулировка вопроса создает у меня впечатление, что вы что-то неправильно поняли... В старых версиях C разрешалось опускать тип возвращаемого значения в объявлении функции, но вам все равно нужно было иметь оператор return
, если вы хотите чтобы вернуть значение. Например, f() { return 1; }
было бы действительным. Хотя некоторые компиляторы все еще позволяют это, в стандарте C этого уже давно нет.
Также обратите внимание, что до появления стандарта C23 отсутствие объявления каких-либо аргументов (даже void
) означало бы, что функция может принимать любое количество аргументов неизвестного типа.
Функции возвращают значение, помещая его в согласованное место, чтобы вызывающий объект мог его найти. printf
сделал это. Если значение не возвращается, предыдущее возвращаемое значение все еще может быть там.
«Она возвращает тип возвращаемого значения функции printf?!» — Возвращаемое значение — это количество символов, переданных в выходной поток, или отрицательное значение в случае ошибки вывода или ошибки кодирования.
Это происходит потому, что сгенерированный машинный код не очищает память стека: оставляя значение, возвращаемое printf, в стеке, программа обрабатывает это значение как возвращаемое значение. Аналогично, если вы напишете что-то вроде этого
#include <stdio.h>
int l()
{
return 3;
}
int m()
{
printf("Hello");
l();
}
void main()
{
int k = m();
printf("%d",k);
}
вывод всегда будет «Hello3». Если вы хотите очистить память стека, вам нужно изменить соглашение о вызовах функции m().
В большинстве реализаций возвращаемые значения функции передаются через регистры, а не в стек.
вывод всегда будет «Hello3». Возможно, но случиться может всякое. Если вы хотите это исправить, то не путем изменения соглашения о вызовах.
Это выглядит почти правильно, но я понимаю, что сказал @TomKarzes, и это кажется важным моментом. Когда вызывается такая функция, как printf, она возвращает целочисленное значение (количество напечатанных символов) в определенном регистре ЦП. Если функция m не указывает возвращаемое значение, компилятор не очищает и не изменяет этот регистр перед возвратом из m. Следовательно, вызывающая функция (main) интерпретирует это оставшееся значение как возвращаемое значение m. Итак, на самом деле значение, возвращаемое printf, находится в регистрах.
@RygelKenoid В принципе это верно, но полагаться на это небезопасно. С точки зрения компилятора, регистр возвращаемого значения мертв, и он может свободно хранить в нем что-то еще. И в тех случаях, когда это действительно сработает, вы, вероятно, получите идентичный код, если просто сохраните возвращаемое значение в переменной, а затем вернете эту переменную.
Если ваша функция не имеет явного оператора
return
и вызывающий код пытается использовать то, что «возвращает» функция, у вас будет неопределенное поведение. Компилятор должен пожаловаться на отсутствие оператораreturn
, а если нет, то включить дополнительные предупреждения и рассматривать их как ошибки, которые необходимо исправить.