Difference between exit() and return in main() function in C
我已经浏览了链接,退出和返回之间有什么区别?和在main()中返回语句vs exit()。找到答案,但徒劳无功。
第一个链接的问题是,答案假设来自任何函数的
第二个链接的问题是我对C++中发生的事情不感兴趣。我想要一个与C有关的答案。
编辑:经过一个人的推荐,我实际上尝试比较以下程序的汇编输出:
注:使用
程序Mainf.c:
1 2 3 | int main(void){ return 0; } |
程序集输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | .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 |
主程序1.c:
1 2 3 4 5 |
程序集输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | .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 |
注意到我对汇编不太熟悉,我可以看到两个
免责声明:此答案不引用C标准。
DR这两种方法都会跳到glibc代码中,为了准确地知道代码在做什么,或者哪个方法更快或更高效,您需要阅读它们。如果您想了解更多关于glibc的信息,应该检查gcc和glibc的来源。最后还有一些链接。
系统调用、包装和glibc第一:出口(3)和出口(2)有区别。第一个是围绕第二个的glibc包装,这是一个系统调用。我们在程序中使用的,并且需要包含
现在,程序不仅仅是简单的指令。它们包含了大量glibc自己的指令。这些glibc函数用于与加载和提供所使用的库功能相关的多个目的。要想让它起作用,glibc必须"在"你的程序中。
那么,glibc是如何进入你的程序的?好吧,它通过编译器把自己放在那里(它设置了一些静态代码和一些动态库的钩子)-很可能您使用的是gcc。
"返回0;"方法我想你知道什么是堆栈帧,所以我不解释它们是什么。值得注意的是,
让我们编译以下内容:
1 2 3 4 | int main(void) { return 0; } |
并编译和调试它:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | $ 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 ... |
现在,
您必须注意的是使用此方法调用函数的顺序。你看,
函数调用的顺序大致如下:
1 2 3 4 5 6 7 | __libc_start_main exit __run_exit_handlers _dl_fini rtld_lock_default_lock_recursive _dl_fini _dl_sort_fini |
"exit(0);"方法
编译如下:
1 2 3 4 5 |
编译和调试…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | $ 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 ... |
我得到的功能序列是:
1 2 3 4 5 6 7 8 9 |
列出对象的符号
有一个很酷的工具可以打印二进制文件中定义的符号。它是NM。我建议你仔细研究一下,因为它会让你知道在一个简单的程序中添加了多少"垃圾"。
要以最简单的形式使用它:
1 2 | $ nm main $ nm exit |
它将打印文件中的符号列表。请注意,此列表不包含这些函数将生成的引用。因此,如果列表中的给定函数调用另一个函数,则另一个函数可能不在列表中。
结论这在很大程度上取决于glibc选择如何处理从
最后,要真正回答您的问题:这两种方法都会跳到glibc代码中,为了确切地知道代码在做什么,您需要阅读它。如果您想了解更多关于glibc的信息,应该检查gcc和glibc的来源。
工具书类- glibc源代码库:查看
stdlib/exit.c 和stdlib/exit.h 中的实现。 - Linux内核出口定义:查看
kernel/exit.c 中的_exit(2) 系统调用实现,以及include/syscalls.h 中的预处理器魔力。 - gcc来源:我不知道EDOCX1(编译器,不是套件)来源,如果有人能指出运行时序列的定义,我会很感激。
只要
根据C11标准:
5.1.2.2.3 Program termination
1 If the return type of the
main function is a type compatible withint , a return from the initial call to themain function is equivalent to calling theexit function with the value returned by themain function as its argument; reaching the} that terminates themain function returns a value of 0. If the return type is not compatible withint , the termination status returned to the host environment is unspecified.
从功能上讲,从
但是,
存在技术差异。如果将以下内容编译为assembly:
1 2 3 4 | int main() { return 1; } |
该代码的最后部分将是:
1 2 3 4 | movl $1, %eax movl $0, -4(%rbp) popq %rbp retq |
另一方面,以下代码编译为程序集:
1 2 3 4 5 |
各方面均相同,但结尾如下:
1 2 3 4 | subq $16, %rsp movl $1, %edi movl $0, -4(%rbp) callq _exit |
除了1被放入
在
1 2 3 4 5 6 7 8 9 |
在函数中:
1 2 3 4 5 6 7 8 9 | void f() { // code return; // return to where it was called from. } void f() { // code exit(0); // terminates program } |
在
如果你做过以下事情,这很重要:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | #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 ", j*24, j*24+23, space + j * 24); } printf("Catastrophic elegance "); } 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 "); if (argc > 1) return 1; else exit(2); } |
因为现在从名为
我打电话给
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | $ ./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 $ |
当用至少一个参数调用时,事情变得混乱(
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | $ ./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 $ |
大多数时候,这类事情不是问题。然而,当它重要的时候,它真的很重要。而且,请注意,在程序退出之前,它不会作为一个问题出现——这会使调试变得很困难。