为什么main在这里不返回0?


116

我刚读

ISO / IEC 9899:201x委员会草案-2011年4月12日

我在5.1.2.2.3中发现程序终止

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

这意味着,如果您未在中指定任何return语句main(),并且程序成功运行,则main的右括号}将返回0。

但是在下面的代码中,我没有指定任何return语句,但是它没有返回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有耐心阅读规格..
Asher

16
gcc本身(对于4.6.2版)可以编译非常相似但不完全像C的语言。它可以编译GnuC89-一种基于C89的“宽松”语言。
2011年

2
return语句中的括号sum()是不必要的。 int main()应该是int main(void)
基思·汤普森

24
混乱!=错字。在我的键盘上,“ 0”和“ o”足够接近,可以轻松地成为后者。;-)
The111 2011年

2
IMHO是一个非常愚蠢的规范,因为它强制编译器通过添加隐式“ return 0”以特殊方式管理“ main”函数。因此,名为“ main”的函数的行为略有不同。编译时检查怎么样(“无返回值”类似)?
Giuseppe Guerrini

Answers:


141

该规则是在C标准的1999版本中添加的。在C90中,返回的状态未定义。

您可以通过传递-std=c99给gcc 来启用它。

附带说明一下,有趣的是返回了9,因为它的返回值printf只是写了9个字符。


40
或者您可以return 0;在结束之前添加}。它无害,并使您的程序可移植到较旧的编译器中。
基思·汤普森

2
@ Mr.32:很好的观察,printf()返回字符串的长度,因此它是9(它是main的“返回值”)(不使用-std = c99)。
希瑟姆

1
@cnicutar:函数通常不会在堆栈上返回较小的值,因为它涉及到弹出,推入和跳转,而不仅仅是移动和返回,因此几乎可以肯定是寄存器,eax特别是在x86上。
乔恩·普迪

8
是的,x86 API通常通过eax寄存器返回类似值的整数。有关更多信息,请参见en.wikipedia.org/wiki/X86_calling_conventions#cdecl
西尔文·德夫雷斯内

2
几次我都看过类似“ int foo(void){bar();}”的代码,其中意在使用“ return bar()”。尽管存在明显的错误,该代码在大多数处理器上都可以正常工作。
ugoren 2012年

15

它返回的返回值printf是实际打印的字符数。


该问题不涉及执行程序时控制台中打印的内容,而是涉及程序的返回值:(您可以在Linux中使用schell命令:echo $?在Windows中获取它,并在Windows中使用:echo%errorlevel% )
Hicham

1
@eharvest虽然我不知道如何%errorlevel%在Windows中进行检查,但是退出代码和mainLinux中的return值之间有何区别?
2011年

1
@eharvest:答案也没有提到控制台中打印的内容。它谈论的返回值printf,在这种情况下是9然后把它从“提升”作为退出代码main使用某些版本的GCC时,不知何故。
AH

抱歉。我的错。你是对的。我读这个答案的速度太快了:/
Hicham

1
请注意,这不能保证。在C89 / C90中,这种情况下返回的状态是不确定的;可能是任何东西。它恰好返回9,因为编译器不努力返回任何其他内容。其他编译器的行为可能会有所不同。
基思·汤普森

6

函数的返回值通常存储在cpu的eax寄存器中,因此语句“ return 4;”。通常会编译为

mov eax, 4;
ret;

并返回x(取决于您的编译器)将类似于:

mov eax, [ebp + 4];
ret;

如果您未指定返回值,则编译器仍会吐出“ ret”,但不会更改eax的值。因此,调用者会认为以前在eax寄存器中剩下的就是返回值。对于此示例,通常为返回值printf,但是不同的编译器将生成不同的机器代码,并以不同的方式使用某些寄存器。

这是一个简化的解释,不同的调用约定和目标平台将发挥至关重要的作用,但是应该有足够的信息来解释示例中“幕后”的情况。

如果您对汇编器有基本的了解,则值得比较不同编译器的反汇编。您可能会发现某些编译器正在清除eax寄存器作为一种保护措施。


11
编译器可能会这样做。它是未定义的。相反,它可能选择使用墨盒将您射入头部。
Lightness Races in Orbit

@LightnessRacesinOrbit未定义与不可预测是不相同的,即从“如果编译器执行X,将符合标准”,它在逻辑上就不会遵循“编译器可能会执行X”
Owen

@Owen:引用定义此内容的标准段落。
Lightness Races in Orbit

2
@LightnessRacesinOrbit对不起,我不太了解。我想我真正想说的是,我认为此答案中提供的诸如noggin182之类的深入了解对于调试非常有用。当您的程序产生意外结果时,很多时候您都不知道它们来自哪里,甚至不知道代码在哪里,并且了解实现细节可以为您指明正确的方向。
Owen

@Owen我从没有宣称过
Lightness Races in Orbit
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.