Dlaczego main nie zwraca tutaj 0?

116

Właśnie czytałem

ISO / IEC 9899: Projekt Komisji 201x - 12 kwietnia 2011 r

w którym znalazłem w punkcie 5.1.2.2.3 Zakończenie programu

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

oznacza to, że jeśli nie określisz żadnej instrukcji return w main() i program zostanie uruchomiony pomyślnie, to w nawiasie zamykającym} main zwróci 0.

Ale w poniższym kodzie nie określam żadnej instrukcji return, ale nie zwraca ona 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);
}

skompilować

gcc test.c  
./a.out
sum is 15
echo $?
9          // here it should be 0 but it shows 9 why?
Jeegar Patel
źródło
69
+1 za cierpliwość do czytania specyfikacji .....
Asher,
16
gccsamodzielnie (dla wersji 4.6.2) kompiluje język bardzo podobny, ale nie całkiem podobny do C. Kompiluje GnuC89 - język „luźno” oparty na C89.
pmg
2
Nawiasy na returnwyciągu w sum()są niepotrzebne. int main()powinno być int main(void).
Keith Thompson,
24
Zamieszanie! = Literówka. Na mojej klawiaturze „0” i „o” są wystarczająco blisko, aby łatwo było to drugie. ;-)
The111
2
IMHO jest dość głupią specyfikacją, ponieważ wymusza na kompilatorze zarządzanie funkcją „main” w specjalny sposób przez dodanie niejawnego „return 0”. Dlatego funkcja o nazwie „main” zachowuje się w nieco inny sposób. A co z kontrolą czasu kompilacji („brak wartości zwracanej” podobnie)?
Giuseppe Guerrini

Odpowiedzi:

141

Zasada ta została dodana w wersji normy C. z 1999 roku. W C90 zwracany status jest niezdefiniowany.

Możesz to włączyć, przechodząc -std=c99 do gcc.

Na marginesie, co ciekawe, zwrócono 9, ponieważ jest to powrót, printfktóry właśnie napisał 9 znaków.

cnicutar
źródło
40
Lub możesz po prostu dodać return 0;przed zamknięciem }. Jest nieszkodliwy i sprawia, że ​​program można przenosić na starsze kompilatory.
Keith Thompson,
2
@ Mr.32: dobra obserwacja, printf () zwraca długość łańcucha, więc 9 jest „powrotem” main (bez użycia -std = c99).
Hicham
1
@cnicutar: Funkcje zazwyczaj nie zwracają małych wartości na stosie - ponieważ wymagałoby to popu, pchnięcia i skoku, a nie tylko ruchu i powrotu - więc prawie na pewno jest to rejestr, eaxszczególnie na x86.
Jon Purdy,
8
Tak, interfejs API x86 zwykle zwraca za pośrednictwem eaxrejestru liczby całkowite, takie jak wartość . Więcej informacji można znaleźć pod adresem en.wikipedia.org/wiki/X86_calling_conventions#cdecl .
Sylvain Defresne,
2
Kilka razy widziałem kod taki jak „int foo (void) {bar ();}”, gdzie „return bar ()” było zamierzone. Ten kod działa dobrze na większości procesorów, pomimo oczywistego błędu.
ugoren
15

Zwraca wartość, printfktóra jest liczbą rzeczywiście wydrukowanych znaków.

Summer_More_More_Tea
źródło
pytanie nie mówi o tym, co jest wypisywane w konsoli, podczas wykonywania programu, mówi o zwracanej wartości programu: (w linuksie można to pobrać za pomocą polecenia schell: echo $? aw oknach za pomocą: echo% errorlevel% )
Hicham
1
@eharvest Chociaż nie wiem, jak sprawdzić %errorlevel%w systemie Windows, jest jakaś różnica między kodem zakończenia a wartością zwracaną w systemie mainLinux?
Summer_More_More_Tea
1
@eharvest: Odpowiedź nie mówi również o tym, co jest wydrukowane w konsoli. Mówi o wartości zwracanej z printfktórym w tym przypadku jest 9, który następnie pobiera „promowane” jako kod wyjścia z mainjakoś podczas korzystania z niektórych wersjach GCC.
AH
Przepraszam. mój błąd. masz rację. Za szybko przeczytałem tę odpowiedź: /
Hicham
1
Pamiętaj, że nie jest to gwarantowane. W C89 / C90 status zwrócony w tym przypadku jest niezdefiniowany ; to może być cokolwiek. Zdarza się, że zwraca 9, ponieważ kompilator nie podjął żadnego wysiłku, aby zwrócić cokolwiek innego. Inne kompilatory prawdopodobnie będą się zachowywać inaczej.
Keith Thompson,
6

Wartość zwracana przez funkcję jest zwykle przechowywana w rejestrze eax procesora, więc instrukcja „return 4;” zwykle kompilowałby się do

mov eax, 4;
ret;

a zwrot x (w zależności od kompilatora) wyglądałby tak:

mov eax, [ebp + 4];
ret;

jeśli nie określisz wartości zwracanej, kompilator nadal będzie wypluwał „ret”, ale nie zmieni wartości eax. Więc dzwoniący pomyśli, że to, co zostało wcześniej w rejestrze eax, jest wartością zwracaną. W tym przykładzie byłaby to zwykle wartość zwracana printf, ale różne kompilatory generują inny kod maszynowy i inaczej używają niektórych rejestrów.

To jest uproszczone wyjaśnienie, różne konwencje wywoływania i platformy docelowe będą odgrywać istotną rolę, ale powinno to wystarczyć do wyjaśnienia, co dzieje się „za kulisami” w twoim przykładzie.

Jeśli masz podstawową wiedzę na temat asemblera, warto porównać dezasemblację różnych kompilatorów. Może się okazać, że niektórzy kompilatorzy czyszczą rejestr eax jako zabezpieczenie.

noggin182
źródło
11
Kompilator może to zrobić. To jest nieokreślone. Zamiast tego może strzelić ci w głowę wkładem do drukarki.
Wyścigi lekkości na orbicie
@LightnessRacesinOrbit undefined to nie to samo, co nieprzewidywalne, tj. Od „jeśli kompilator ma X, będzie zgodny ze standardem”, nie będzie logicznie przestrzegać „kompilator może zrobić X”
Owen
@ Owen: Cytuj standardowy fragment, który to definiuje.
Wyścigi lekkości na orbicie
2
@LightnessRacesinOrbit Przepraszam, nie całkiem śledzę. Myślę, że naprawdę chcę powiedzieć, że uważam, że spostrzeżenia, takie jak noggin182 podane w tej odpowiedzi, są niezwykle przydatne do debugowania. Kiedy program daje nieoczekiwane wyniki, często nie wiesz, skąd one pochodzą, a nawet gdzie w kodzie szukać, a zrozumienie szczegółów implementacji może wskazać właściwy kierunek.
Owen
@Owen Nigdy nie twierdziłem inaczej
Lightness Races in Orbit