退出()和C中main()函数的返回之间的区别

时间:2016-02-18 02:47:43

标签: c

我查看了链接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个程序之间存在一些差异。有什么区别?

5 个答案:

答案 0 :(得分:5)

免责声明:此答案未引用C标准。

TL; DR

两种方法跳转 GLibC代码,并且确切地知道该代码正在做什么或者哪一个更快或更有效,您需要读他们。如果您想了解有关GLibC的更多信息,您应该检查GCC和GLibC的来源。最后有链接。

Syscalls,wrappers和GLibC

首先: exit(3)_exit(2)之间存在差异。第一个是围绕第二个的GLibC包装器,它是system call。我们在程序中使用的那个,并要求包含stdlib.h exit(3) - GLibC包装器,系统调用。

现在,程序不是只是您的简单说明。它们包含大量的GLibC 自己的指令。这些GLibC函数有多种与加载和提供您使用的库功能相关的用途。为了工作,GLibC必须是&#34;内部&#34;你的计划。

那么,GLibC如何在你的程序中?好吧,它通过你的编译器(它设置一些静态代码和一些钩子到动态库)放在那里 - 很可能你正在使用gcc

&#39;返回0;&#39;方法

我想你知道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;

&#39;退出(0);&#39;方法

编译:

__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的来源。

参考

  • GLibC Source Repository:查看_exit(2)stdlib/exit.c了解实施情况。
  • Linux Kernel Exit Definition:查看stdlib/exit.h kernel/exit.c系统调用实现,_exit(2)查看后面的预处理器魔法。
  • GCC Sources:我不知道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
$

大多数时候,这种事情并不是问题。然而,当它重要时,它确实很重要。并且,请注意,在程序退出之前,它不会被视为一个问题 - 这可能会使调试变得棘手。