ChatGPT解决这个技术问题 Extra ChatGPT

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

我只是在阅读

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

我在 5.1.2.2.3 程序终止下找到

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

这意味着如果你没有在 main() 中指定任何 return 语句,并且如果程序运行成功,那么 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?
+1 有耐心阅读规格.....
gcc 本身(对于 4.6.2 版)编译的语言非常相似,但不太像 C。它编译 GnuC89——一种“松散地”基于 C89 的语言。
sum() 中的 return 语句上的括号是不必要的。 int main() 应该是 int main(void)
混乱!=错字。在我的键盘上,“0”和“o”足够接近,很容易成为后者。 ;-)
恕我直言,这是一个非常愚蠢的规范,因为它强制编译器通过添加隐式“return 0”以特殊方式管理“main”函数。因此,名为“main”的函数的行为方式略有不同。编译时检查(“无返回值”类似)怎么样?

K
Keith Thompson

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

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

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


或者您可以在结束 } 之前添加 return 0;。它是无害的,并使您的程序可移植到较旧的编译器。
@ Mr.32:很好的观察, printf() 返回字符串的长度,所以它是 9,这是 main 的“返回”(不使用 -std=c99)。
@cnicutar:函数通常不会在堆栈上返回小的值——因为它会涉及弹出、推送和跳转,而不仅仅是移动和返回——所以它几乎肯定是一个寄存器,eax 特别是在 x86 .
是的,x86 API 通常通过 eax 寄存器返回类似整数的值。有关详细信息,请参阅 en.wikipedia.org/wiki/X86_calling_conventions#cdecl
有好几次我看到像“int foo(void) { bar(); }”这样的代码,其中“return bar()”的意思是。尽管存在明显的错误,但此代码在大多数处理器上都可以正常工作。
S
Summer_More_More_Tea

它返回 printf 的返回值,这是实际打印出来的字符数。


问题不涉及执行程序时控制台中打印的内容,而是讨论程序的返回值:(您可以在 linux 中使用 schell 命令获取它:echo $? 和在 Windows 中使用:echo %errorlevel% )
@eharvest 虽然我不知道如何在 Windows 中检查 %errorlevel%,但在 linux 中退出代码和 main 的返回值有什么区别?
@eharvest:答案也没有谈论控制台中打印的内容。它讨论了 printf返回值,在这种情况下是 9,然后在使用某些 gcc 版本时以某种方式“提升”为 main 的退出代码。
对不起。我的错。你说的对。我读这个答案太快了:/
请注意,这不能保证。在 C89/C90 中,这种情况下返回的状态是未定义的;它可以是任何东西。它恰好返回 9,因为编译器没有努力返回任何其他内容。其他编译器的行为可能会有所不同。
n
noggin182

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

mov eax, 4;
ret;

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

mov eax, [ebp + 4];
ret;

如果你没有指定返回值,那么编译器仍然会吐出“ret”,但不会改变 eax 的值。所以调用者会认为之前留在 eax 寄存器中的是返回值。对于这个例子,它通常是返回值 printf,但不同的编译器会生成不同的机器代码并以不同的方式使用一些寄存器。

这是一个简化的解释,不同的调用约定和目标平台将发挥至关重要的作用,但它应该足以解释您的示例中“幕后”发生的事情。

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


编译器可能会这样做。它是未定义的。相反,它可能会选择用打印机墨盒朝你的头部射击。
@LightnessRacesinOrbit undefined 与不可预测不同,即从“如果编译器执行 X,它将符合标准”,它在逻辑上不遵循“编译器可能执行 X”
@Owen:引用定义这一点的标准段落。
@LightnessRacesinOrbit 很抱歉,我不太了解。我想我真正想说的是,我认为这个答案中提供的诸如 noggin182 之类的底层见解对于调试非常有用。当你的程序产生意想不到的结果时,很多时候你不知道它们来自哪里,甚至不知道在代码中的什么位置,了解实现细节可以为你指明正确的方向。
@Owen 我从来没有声称其他