Difference between exit () function and return in main () in C

I looked through the links . What is the difference between exit and return? and also return statement vs exit () in main () to find the answer, but to no avail.

The problem with the first link is that the response accepts return from any function. I want to know the exact difference between the two in the main () function. Even if there is a slight difference, I would like to know what it is. Which is preferable and why? Is there any performance gain when using return over exit () (or exit () over return ) when all compiler optimizations are turned off?

The problem with the second link - I'm not interested in knowing what is happening in C ++. I want the answer specifically related to C.

EDIT: After recommending the person, I tried to compare the assembly of the following programs:

Note. Using gcc -S <myprogram>.c

Mainf.c program:

 int main(void){ return 0; } 

Assembly:

  .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 program:

 #include <stdlib.h> int main(void){ exit(0); } 

Assembly:

  .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 

Noting that I am not very versed in the assembly, I see some differences between the two programs with a version of exit() shorter than the version of return . Who cares?

+6
source share
5 answers

Disclaimer: This answer does not cite C. standards.

TL DR

Both methods go into GLibC code , and to know exactly what this code does or which one is faster or more efficient, you need to read them. If you want to know more about GLibC, you should check out the sources for GCC and GLibC. There are links at the end.


Syscalls, wrappers and GLibC

First: there is a difference between exit (3) and _ exit (2) . The first one is the GLibC shell , which is a call system. The one we use in our program also requires the inclusion of stdlib.h exit(3) - the GLibC shell, not a system call.

Now programs are not just your simple instructions. They contain heavy loads of GLibC native instructions. These GLibC functions perform several tasks related to loading and providing the library functions that you use. For this, GLibC must be "inside" your program.

So how is GLibC inside your program? Well, it is placed there through your compiler (it installs some static code and intercepts some in a dynamic library) - most likely you are using gcc .


'return 0;' Method

I suppose you know that the frames are stacked , so I won’t explain what they are. The great thing to notice is that main() itself has its own stack stack. And this stack stack comes back somewhere , and it should come back ... But, to where ?

Let's compile the following:

 int main(void) { return 0; } 

And compile and debug it with

 $ 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 ... 

Now stepi will do for the fun part. This will say one command at a time, so it is perfect for making function calls. After you first press stepi , just keep your finger on ENTER until you get tired.

What you should observe is the sequence in which functions are called using this method. You see, ret is a jump instruction ( edit: after David Hoelzer , I see that calling ret simple jump is an over-generalization): after we pop rbp , ret itself put the return pointer from the stack and move to it. So, if GLibC built this stack frame, retq does our return 0; C directly into GLibC native code! How smart!

The order of function calls began like this:

 __libc_start_main exit __run_exit_handlers _dl_fini rtld_lock_default_lock_recursive _dl_fini _dl_sort_fini 

'exit (0);' Method

Compilation:

 #include <stdlib.h> int main(void) { exit(0); } 

And compiling and debugging ...

 $ 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 ... 

And the resulting sequence of functions:

 exit@plt ?? _dl_runtime_resolve _dl_fixup _dl_lookup_symbol_x do_lookup_x check_match _dl_name_match strcmp 

List of objects Symbols

There is a cool tool for printing characters defined in binary format. This is nm . I suggest you look into it, as this will give you an idea of ​​how many β€œcrap” has been added in a simple program like the one above.

To use it in its simplest form:

 $ nm main $ nm exit 

This will print a list of characters in the file. Please note that there are no links in this list that will perform these functions. Therefore, if a given function in this list calls another function, the other probably will not be in the list.


Conclusion

This depends heavily on how GLibC wants to handle the simple stack stack from main and how it implements the exit wrapper. As a result, the _exit(2) system call will be called, and you will exit the process.

Finally , to really answer your question: both methods go into GLibC code and know exactly what this code does, you need to read it. If you want to know more about GLibC, you should check out the sources for GCC and GLibC.


References

  • GLibC Source Repository : Browse stdlib/exit.c and stdlib/exit.h for implementations.
  • Defining Linux kernel output : see kernel/exit.c for the _exit(2) system call and include/syscalls.h for the preprocessor magic behind it.
  • GCC Sources : I do not know the sources of gcc (compiler, not set), and would appreciate if anyone could indicate where the execution sequence is defined.
+5
source

Functionally, from the main() function there really is no difference in C. For example, even if you define a function handler with a library call atexit() , both return() and exit() from main will call this function pointer.

However, calling exit() has the flexibility you can use to force a program to exit with a return code from anywhere in the code.

There are technical differences. If you assembled the following assembly:

 int main() { return 1; } 

the end of this code will be:

 movl $1, %eax movl $0, -4(%rbp) popq %rbp retq 

On the other hand, the following code compiled for assembly:

 #include<stdlib.h> int main() { exit(1); } 

will be identical in all respects, except that it ends as follows:

 subq $16, %rsp movl $1, %edi movl $0, -4(%rbp) callq _exit 

Besides the fact that 1 is placed in EDI , not EAX , as required on the platform where I compiled this code as the calling convention for calling _exit , you will notice two differences. First, the stack alignment operation is performed to prepare a function call. Secondly, instead of ending with retq , we now call into the system library, which will process the final return code and return.

+4
source

There is practically no difference between calling exit or executing return from main , while main returns an int compatible type.

From the C11 standard:

5.1.2.2.3 End of program

1 If the return type of the main function is an int compatible type, returning from the initial call to the main function is equivalent to calling the exit function with the value returned by the main function as an argument; achievement } , which terminates the main function, returns 0. If the return type is incompatible with int , the completion status returned to the host environment is not specified.

+4
source

exit is a system call, and return is a language instruction.

exit terminates the current process, return returns from the function call.

In the main() function, they do the same thing:

 int main() { // code return 0; } int main() { // code exit(0); } 

In function:

 void f() { // code return; // return to where it was called from. } void f() { // code exit(0); // terminates program } 
+3
source

One significant difference between using return and calling exit() in main() is that if you call exit() , the local variables in main() still exist and are valid, whereas if you return , it is not.

This is important if you have done something like:

 #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); } 

because now the function called (via atexit() ) from the startup code called main() does not have a valid buffer for standard output. Regardless of whether the crash is either just getting confused or printing garbage or seems to work, you can discuss it.

I called the hysteresis program. When starting without arguments, it used exit() and worked correctly / safely (the local variable space in function_using_stdout() did not use the space with the I / O buffer for stdout ):

 $ ./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 $ 

When called with at least one argument, everything went wrong (the local variable space in function_using_stdout() probably shared the space with the I / O buffer for stdout ), unless it is used by code that executes functions registered with 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 $ 

In most cases, this kind of problem is not a problem. However, when it matters, it does matter. And, note that this is not visible as a problem until the program exits - which may make it difficult to debug it.

+2
source

All Articles