Почему main не возвращает здесь 0?


116

Я просто читал

ISO / IEC 9899: проект комитета 201x - 12 апреля 2011 г.

в котором я нашел в разделе 5.1.2.2.3 Прекращение программы

..reaching the } that terminates the main function returns a value of 0. 

это означает, что если вы не укажете какой-либо оператор return в main(), и если программа будет успешно выполнена, то в закрывающей скобке} main вернет 0.

Но в следующем коде я не указываю никакого оператора возврата, но он не возвращает 0

#include<stdio.h>
int sum(int a,int b)
{
return (a + b);
}

int main()
{
    int a=10;
    int b=5;
    int ans;    
    ans=sum(a,b);
    printf("sum is %d",ans);
}

компиляции

gcc test.c  
./a.out
sum is 15
echo $?
9          // here it should be 0 but it shows 9 why?

69
+1 за терпение читать спецификации .....
Ашер

16
gccсам по себе (для версии 4.6.2) компилирует язык, очень похожий, но не совсем похожий на C. Он компилирует GnuC89 - язык, "слабо" основанный на C89.
pmg

2
Скобки в returnоператоре sum()не нужны. int main()должно быть int main(void).
Кейт Томпсон

24
Путаница! = Опечатка. На моей клавиатуре «0» и «o» достаточно близки, чтобы легко оказаться последним. ;-)
The111

2
ИМХО - это довольно глупая спецификация, поскольку она заставляет компилятор особым образом управлять «основной» функцией, добавляя неявное «return 0». Таким образом, функция с именем «main» ведет себя немного иначе. А как насчет проверок во время компиляции (аналог «без возврата»)?
Джузеппе Геррини

Ответы:


141

Это правило было добавлено в версию стандарта C. В C90 возвращаемый статус не определен.

Вы можете включить его, перейдя -std=c99в gcc.

В качестве примечания, что интересно, возвращается 9, потому что это возврат, printfкоторый только что написал 9 символов.


40
Или вы можете просто добавить return 0;перед закрытием }. Это безвредно и делает вашу программу переносимой на старые компиляторы.
Кейт Томпсон

2
@ Mr.32: хорошее наблюдение, printf () возвращает длину строки, поэтому она равна 9, что является «возвратом» основного (без использования -std = c99).
Hicham

1
@cnicutar: функции обычно не возвращают небольшие значения в стеке - потому что это будет включать в себя нажатие, толчок и прыжок, а не просто движение и возврат - так что это почти наверняка регистр, eaxособенно на x86.
Джон Парди,

8
Да, API x86 обычно возвращает целочисленное значение через eaxрегистр. См. En.wikipedia.org/wiki/X86_calling_conventions#cdecl для получения дополнительной информации.
Сильвен Дефресн

2
Несколько раз я видел код типа int foo (void) {bar ();}, где имелось в виду return bar (). Этот код отлично работает на большинстве процессоров, несмотря на очевидную ошибку.
ugoren 03

15

Он возвращает возвращаемое значение, printfкоторое является количеством действительно распечатанных символов.


вопрос не говорит о том, что печатается в консоли при выполнении программы, он говорит о возвращаемом значении программы: (вы можете получить его в linux с помощью команды schell: echo $? и в windows с помощью: echo% errorlevel% )
Hicham

1
@eharvest Хотя я не знаю, как проверить %errorlevel%в Windows, есть ли разница между кодом выхода и возвращаемым значением mainв Linux?
Summer_More_More_Tea

1
@eharvest: Ответ также не говорит о том, что печатается в консоли. Это говорит о возвращаемом значении из printfкоторых в данном случае является 9 , который затем получает «повышен» в качестве кода выхода из mainпри использовании некоторых версий GCC так или иначе.
AH

Прости. моя ошибка. Ты прав. я прочитал этот ответ слишком быстро: /
Hicham

1
Учтите, что это не гарантируется. В C89 / C90 статус, возвращаемый в этом случае, не определен ; это могло быть что угодно. Это происходит потому, что компилятор не пытается вернуть что-либо еще. Другие компиляторы, вероятно, будут вести себя иначе.
Кейт Томпсон

6

Значение, возвращаемое функцией, обычно сохраняется в регистре eax процессора, поэтому оператор return 4; обычно компилируется в

mov eax, 4;
ret;

и вернуть x (в зависимости от вашего компилятора) будет примерно так:

mov eax, [ebp + 4];
ret;

если вы не укажете возвращаемое значение, компилятор все равно выдаст "ret", но не изменит значение eax. Таким образом, вызывающий будет думать, что все, что было ранее оставлено в регистре eax, является возвращаемым значением. В этом примере обычно будет возвращаемое значение printf, но разные компиляторы будут генерировать другой машинный код и по-разному использовать некоторые регистры.

Это упрощенное объяснение, различные соглашения о вызовах и целевые платформы будут играть жизненно важную роль, но этой информации должно быть достаточно, чтобы объяснить, что происходит «за кулисами» в вашем примере.

Если у вас есть базовые представления об ассемблере, стоит сравнить дизассемблирование разных компиляторов. Вы можете обнаружить, что некоторые компиляторы очищают регистр eax в качестве меры предосторожности.


11
Компилятор может это сделать. Это не определено. Вместо этого он может выстрелить вам в голову из картриджа для принтера.
Lightness Races in Orbit

@LightnessRacesinOrbit undefined - это не то же самое, что и непредсказуемый, то есть из «если компилятор выполняет X, он будет соответствовать стандарту», ​​это не логически следует из «компилятор может сделать X»
Оуэн

@Owen: Процитируйте стандартный отрывок, определяющий это.
Гонки за легкостью на орбите

2
@LightnessRacesinOrbit Извините, я не совсем понимаю. Думаю, что я действительно хочу сказать, так это то, что я думаю, что скрытые идеи, такие как noggin182, представленные в этом ответе, невероятно полезны для отладки. Когда ваша программа дает неожиданные результаты, часто вы не знаете, откуда они берутся или даже где в коде искать, и понимание деталей реализации может указать вам правильное направление.
Оуэн

@Owen Я никогда не утверждал иначе
Гонки
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.