我查看了链接What is the difference between exit and return? 和 return statement vs exit() in main() 找到答案,但徒劳无功。
第一个链接的问题是答案假定来自任何函数的return
。我想知道在main()函数中两者之间的确切差异。即使有一点不同,我也想知道它是什么。哪个是首选,为什么?在关闭各种编译器优化后,使用return
优于exit()(或exit()优于return
)是否有任何性能提升?
第二个链接的问题是我对知道C ++中发生的事情并不感兴趣。我想要特别关于C的答案。
编辑: 经过一个人的推荐,我实际上试图比较以下程序的汇编输出:
注意:使用gcc -S <myprogram>.c
程序mainf.c:
int main(void){
return 0;
}
装配输出:
.file "mainf.c"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 4.9.2-10ubuntu13) 4.9.2"
.section .note.GNU-stack,"",@progbits
编程mainf1.c:
#include <stdlib.h>
int main(void){
exit(0);
}
装配输出:
.file "mainf1.c"
.text
.globl main
.type main, @function
main:
.LFB2:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $0, %edi
call exit
.cfi_endproc
.LFE2:
.size main, .-main
.ident "GCC: (Ubuntu 4.9.2-10ubuntu13) 4.9.2"
.section .note.GNU-stack,"",@progbits
我注意到我不熟悉汇编,我可以看到exit()
版本比return
版本短的2个程序之间存在一些差异。有什么区别?
答案 0 :(得分:5)
免责声明:此答案未引用C标准。
两种方法跳转到 GLibC代码,并且确切地知道该代码正在做什么或者哪一个更快或更有效,您需要读他们。如果您想了解有关GLibC的更多信息,您应该检查GCC和GLibC的来源。最后有链接。
首先: exit(3)和_exit(2)之间存在差异。第一个是围绕第二个的GLibC包装器,它是system call。我们在程序中使用的那个,并要求包含stdlib.h
exit(3)
- GLibC包装器,不系统调用。
现在,程序不是只是您的简单说明。它们包含大量的GLibC 自己的指令。这些GLibC函数有多种与加载和提供您使用的库功能相关的用途。为了工作,GLibC必须是&#34;内部&#34;你的计划。
那么,GLibC如何在你的程序中?好吧,它通过你的编译器(它设置一些静态代码和一些钩子到动态库)放在那里 - 很可能你正在使用gcc。
我想你知道stack frames是什么,所以我不会解释它们是什么。值得注意的是,main()
本身拥有自己的堆栈框架。那个堆栈框架返回的某个地方,它必须返回...但是,哪里?
让我们编译以下内容:
int main(void)
{
return 0;
}
使用以下命令编译和调试:
$ gcc -o main main.c
$ gdb main
(gdb) disass main
Dump of assembler code for function main:
0x00000000004005e8 <+0>: push %rbp
0x00000000004005e9 <+1>: mov %rsp,%rbp
0x00000000004005ec <+4>: mov $0x0,%eax
0x00000000004005f1 <+9>: pop %rbp
0x00000000004005f2 <+10>: retq
End of assembler dump.
(gdb) break main
(gdb) run
Breakpoint 1, 0x00000000004005ec in main ()
(gdb) stepi
...
现在,stepi
将成为有趣的部分。这将一次跳过一条指令,因此它非常适合跟踪函数调用。在您第一次按下stepi
后,只需将手指放在ENTER上,直到您感到疲倦为止。
您必须注意的是使用此方法调用函数的顺序。你看,ret
是一个&#34;跳跃&#34;在David Hoelzer评论之后的编辑,我看到调用ret
一个简单的跳转是一种过度概括):在我们弹出rbp
之后,{{1本身将从堆栈弹出返回指针并跳转到它。因此,如果GLibC构建了该堆栈帧,ret
正在使我们的retq
C语句跳转到GLibC自己的代码中!多么聪明!
我开始的函数调用顺序大致如下:
return 0;
编译:
__libc_start_main
exit
__run_exit_handlers
_dl_fini
rtld_lock_default_lock_recursive
_dl_fini
_dl_sort_fini
编译和调试......
#include <stdlib.h>
int main(void)
{
exit(0);
}
我得到的功能序列是:
$ gcc -o exit exit.c
$ gdb exit
(gdb) disass main
Dump of assembler code for function main:
0x0000000000400628 <+0>: push %rbp
0x0000000000400629 <+1>: mov %rsp,%rbp
0x000000000040062c <+4>: mov $0x0,%edi
0x0000000000400631 <+9>: callq 0x4004d0 <exit@plt>
End of assembler dump.
(gdb) break main
(gdb) run
Breakpoint 1, 0x000000000040062c in main ()
(gdb) stepi
...
这是一个很酷的工具,用于打印二进制文件中定义的符号。它是nm。我建议你看看它,因为它会让你知道多少&#34; crap&#34;它被添加到一个像上面那样的简单程序中。
以最简单的形式使用它:
exit@plt
??
_dl_runtime_resolve
_dl_fixup
_dl_lookup_symbol_x
do_lookup_x
check_match
_dl_name_match
strcmp
这将打印文件中的符号列表。请注意,此列表不包含这些函数将提供的引用。因此,如果此列表中的给定函数调用另一个函数,则另一个函数可能不会出现在列表中。
这在很大程度上取决于GLibC选择处理从$ nm main
$ nm exit
返回的简单堆栈帧的方式以及它如何实现main
包装器。最后,exit
系统调用将被调用,您将退出您的流程。
最后,真正回答你的问题:两种方法都跳转到GLibC代码,并确切地知道代码正在做什么,你需要阅读它。如果您想了解有关GLibC的更多信息,您应该检查GCC和GLibC的来源。
_exit(2)
和stdlib/exit.c
了解实施情况。stdlib/exit.h
kernel/exit.c
系统调用实现,_exit(2)
查看后面的预处理器魔法。include/syscalls.h
(编译器,而不是套件)来源,如果有人能指出运行时序列的定义,我将不胜感激。答案 1 :(得分:4)
从功能上来说,在main()
函数中,C中确实没有区别。例如,即使您使用atexit()
库调用定义了函数处理程序,return()
和{{来自main的1}}将调用该函数指针。
然而,exit()
调用具有灵活性,您可以使用它来使程序从代码中的任何点返回代码退出。
存在技术差异。如果将以下内容编译为汇编:
exit()
该代码的最后部分将是:
int main()
{
return 1;
}
另一方面,以下代码编译为程序集:
movl $1, %eax
movl $0, -4(%rbp)
popq %rbp
retq
除了结尾如下之外,在所有方面都是相同的:
#include<stdlib.h>
int main()
{
exit(1);
}
除了在我编译此代码作为subq $16, %rsp
movl $1, %edi
movl $0, -4(%rbp)
callq _exit
电话的调用约定的平台上需要放入EDI
而不是EAX
的1之外,您还可以使用#{1}}。请注意两个不同之处。首先,进行堆栈对齐操作以准备函数调用。其次,我们现在调用系统库,而不是以_exit
结束,系统库将处理最终的返回代码并返回。
答案 2 :(得分:4)
只要exit
返回与return
兼容的类型,调用main
或从main
执行int
几乎没有区别。< / p>
来自C11标准:
5.1.2.2.3程序终止
1如果
main
函数的返回类型是与int
兼容的类型,则从初始调用到main
函数的返回等同于调用exit
使用main
函数返回的值作为其参数的函数;到达终止}
函数的main
返回值0.如果返回类型与int
不兼容,则返回到主机环境的终止状态未指定。
答案 3 :(得分:3)
exit
是系统调用,而return
是该语言的指令。
exit
终止当前进程,return
从函数调用返回。
在main()
函数中,它们都完成了同样的事情:
int main() {
// code
return 0;
}
int main() {
// code
exit(0);
}
在函数中:
void f() {
// code
return; // return to where it was called from.
}
void f() {
// code
exit(0); // terminates program
}
答案 4 :(得分:2)
在return
计划中使用exit()
和调用main()
之间的一个主要区别是,如果您致电exit()
,main()
中的局部变量仍然存在存在且有效,而如果你return
,则不存在。
如果你做过以下任何事情,这很重要:
#include <stdio.h>
#include <stdlib.h>
static void function_using_stdout(void)
{
char space[512];
char *base = space;
for (int j = 0; j < 10; j++)
{
base += sprintf(base, "Hysterical raisins #%d (continued) ", j+1);
printf("%d..%d: %.24s\n", j*24, j*24+23, space + j * 24);
}
printf("Catastrophic elegance\n");
}
int main(int argc, char **argv)
{
char buffer[64]; // Deliberately rather small
setvbuf(stdout, buffer, _IOFBF, sizeof(buffer));
atexit(function_using_stdout);
for (int i = 0; i < 3; i++)
function_using_stdout();
printf("All done - exiting now\n");
if (argc > 1)
return 1;
else
exit(2);
}
因为现在调用atexit()
的启动代码调用的函数(来自main()
)没有标准输出的有效缓冲区。无论是崩溃还是仅仅彻底混淆或打印垃圾或似乎工作都可以辩论。
我打电话给程序hysteresis
。在没有参数的情况下运行时,它使用了exit()
并正常/正常工作(space
中的本地function_using_stdout()
变量没有与stdout
的I / O缓冲区共享空间:
$ ./hysteresis
'hysteresis' is up to date.
0..23: Hysterical raisins #1 (c
24..47: ontinued) Hysterical rai
48..71: sins #2 (continued) Hyst
72..95: erical raisins #3 (conti
96..119: nued) Hysterical raisins
120..143: #4 (continued) Hysteric
144..167: al raisins #5 (continued
168..191: ) Hysterical raisins #6
192..215: (continued) Hysterical r
216..239: aisins #7 (continued) Hy
Catastrophic elegance
0..23: Hysterical raisins #1 (c
24..47: ontinued) Hysterical rai
48..71: sins #2 (continued) Hyst
72..95: erical raisins #3 (conti
96..119: nued) Hysterical raisins
120..143: #4 (continued) Hysteric
144..167: al raisins #5 (continued
168..191: ) Hysterical raisins #6
192..215: (continued) Hysterical r
216..239: aisins #7 (continued) Hy
Catastrophic elegance
0..23: Hysterical raisins #1 (c
24..47: ontinued) Hysterical rai
48..71: sins #2 (continued) Hyst
72..95: erical raisins #3 (conti
96..119: nued) Hysterical raisins
120..143: #4 (continued) Hysteric
144..167: al raisins #5 (continued
168..191: ) Hysterical raisins #6
192..215: (continued) Hysterical r
216..239: aisins #7 (continued) Hy
Catastrophic elegance
All done - exiting now
0..23: Hysterical raisins #1 (c
24..47: ontinued) Hysterical rai
48..71: sins #2 (continued) Hyst
72..95: erical raisins #3 (conti
96..119: nued) Hysterical raisins
120..143: #4 (continued) Hysteric
144..167: al raisins #5 (continued
168..191: ) Hysterical raisins #6
192..215: (continued) Hysterical r
216..239: aisins #7 (continued) Hy
Catastrophic elegance
$
当使用至少一个参数调用时,事情变得混乱(space
中的本地function_using_stdout()
变量可能与stdout
的I / O缓冲区共享空间 - 除非这是由执行用atexit()
注册的函数的代码使用:
$ ./hysteresis aleph
0..23: Hysterical raisins #1 (c
24..47: ontinued) Hysterical rai
48..71: sins #2 (continued) Hyst
72..95: erical raisins #3 (conti
96..119: nued) Hysterical raisins
120..143: #4 (continued) Hysteric
144..167: al raisins #5 (continued
168..191: ) Hysterical raisins #6
192..215: (continued) Hysterical r
216..239: aisins #7 (continued) Hy
Catastrophic elegance
0..23: Hysterical raisins #1 (c
24..47: ontinued) Hysterical rai
48..71: sins #2 (continued) Hyst
72..95: erical raisins #3 (conti
96..119: nued) Hysterical raisins
120..143: #4 (continued) Hysteric
144..167: al raisins #5 (continued
168..191: ) Hysterical raisins #6
192..215: (continued) Hysterical r
216..239: aisins #7 (continued) Hy
Catastrophic elegance
0..23: Hysterical raisins #1 (c
24..47: ontinued) Hysterical rai
48..71: sins #2 (continued) Hyst
72..95: erical raisins #3 (conti
96..119: nued) Hysterical raisins
120..143: #4 (continued) Hysteric
144..167: al raisins #5 (continued
168..191: ) Hysterical raisins #6
192..215: (continued) Hysterical r
216..239: aisins #7 (continued) Hy
Catastrophic elegance
Al) Hysterical raisins #2 (continued) l raisins #1 (c
24..47: ontinued) Hysterical rai
48..71: l rai
48..71: nued) Hyst
72..95: 71: nued) Hyst
72..95: 7
96..119: nued) Hysterical raisins
120..143: #4 (continued) Hysteric
144..167: al raisins #5 (continued
168..191: ) Hysterical raisins #6
192..215: (continued) Hysterical r
216..239: aisins #7 (continued) Hy
Catastrophic elegance
$
大多数时候,这种事情并不是问题。然而,当它重要时,它确实很重要。并且,请注意,在程序退出之前,它不会被视为一个问题 - 这可能会使调试变得棘手。