Почему 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 символов.

cnicutar
источник
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которое является количеством действительно распечатанных символов.

Summer_More_More_Tea
источник
вопрос не говорит о том, что печатается в консоли при выполнении программы, он говорит о возвращаемом значении программы: (вы можете получить его в 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 в качестве меры предосторожности.

noggin182
источник
11
Компилятор может это сделать. Это не определено. Вместо этого он может выстрелить вам в голову из картриджа для принтера.
Lightness Races in Orbit
@LightnessRacesinOrbit undefined - это не то же самое, что и непредсказуемый, то есть из «если компилятор выполняет X, он будет соответствовать стандарту», ​​это не логически следует из «компилятор может сделать X»
Оуэн
@Owen: Процитируйте стандартный отрывок, определяющий это.
Гонки за легкостью на орбите
2
@LightnessRacesinOrbit Извините, я не совсем понимаю. Думаю, что я действительно хочу сказать, так это то, что я думаю, что скрытые идеи, такие как noggin182, представленные в этом ответе, невероятно полезны для отладки. Когда ваша программа дает неожиданные результаты, часто вы не знаете, откуда они берутся или даже где в коде искать, и понимание деталей реализации может указать вам правильное направление.
Оуэн
@Owen Я никогда не утверждал иначе
Гонки