119

What is difference between return and exit statement in C programming when called from anywhere in a C program?

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
shona
  • 1,277
  • 3
  • 11
  • 5
  • I removed the 'close as duplicate' because the chosen duplicate is tagged with both C and C++ and there is no need to confuse people with the C++ issues (which are different from, though vaguely similar to, the C issues). The duplicate was [return statement vs exit() in main()?](https://stackoverflow.com/questions/461449/return-statement-vs-exit-in-main) – Jonathan Leffler Apr 20 '22 at 03:09

5 Answers5

170
  • return returns from the current function; it's a language keyword like for or break.
  • exit() terminates the whole program, wherever you call it from. (After flushing stdio buffers and so on).

The only case when both do (nearly) the same thing is in the main() function, as a return from main performs an exit().

In most C implementations, main is a real function called by some startup code that does something like int ret = main(argc, argv); exit(ret);. The C standard guarantees that something equivalent to this happens if main returns, however the implementation handles it.

Example with return:

#include <stdio.h>

void f(){
    printf("Executing f\n");
    return;
}

int main(){
    f();
    printf("Back from f\n");
}

If you execute this program it prints:

Executing f
Back from f

Another example for exit():

#include <stdio.h>
#include <stdlib.h>

void f(){
    printf("Executing f\n");
    exit(0);
}

int main(){
    f();
    printf("Back from f\n");
}

If you execute this program it prints:

Executing f

You never get "Back from f". Also notice the #include <stdlib.h> necessary to call the library function exit().

Also notice that the parameter of exit() is an integer (it's the return status of the process that the launcher process can get; the conventional usage is 0 for success or any other value for an error).

The parameter of the return statement is whatever the return type of the function is. If the function returns void, you can omit the return at the end of the function.

Last point, exit() come in two flavors _exit() and exit(). The difference between the forms is that exit() (and return from main) calls functions registered using atexit() or on_exit() before really terminating the process while _exit() (from #include <unistd.h>, or its synonymous _Exit from #include <stdlib.h>) terminates the process immediately.

Now there are also issues that are specific to C++.

C++ performs much more work than C when it is exiting from functions (return-ing). Specifically it calls destructors of local objects going out of scope. In most cases programmers won't care much of the state of a program after the processus stopped, hence it wouldn't make much difference: allocated memory will be freed, file ressource closed and so on. But it may matter if your destructor performs IOs. For instance automatic C++ OStream locally created won't be flushed on a call to exit and you may lose some unflushed data (on the other hand static OStream will be flushed).

This won't happen if you are using the good old C FILE* streams. These will be flushed on exit(). Actually, the rule is the same that for registered exit functions, FILE* will be flushed on all normal terminations, which includes exit(), but not calls to _exit() or abort().

You should also keep in mind that C++ provide a third way to get out of a function: throwing an exception. This way of going out of a function will call destructor. If it is not catched anywhere in the chain of callers, the exception can go up to the main() function and terminate the process.

Destructors of static C++ objects (globals) will be called if you call either return from main() or exit() anywhere in your program. They wont be called if the program is terminated using _exit() or abort(). abort() is mostly useful in debug mode with the purpose to immediately stop the program and get a stack trace (for post mortem analysis). It is usually hidden behind the assert() macro only active in debug mode.

When is exit() useful ?

exit() means you want to immediately stops the current process. It can be of some use for error management when we encounter some kind of irrecoverable issue that won't allow for your code to do anything useful anymore. It is often handy when the control flow is complicated and error codes has to be propagated all way up. But be aware that this is bad coding practice. Silently ending the process is in most case the worse behavior and actual error management should be preferred (or in C++ using exceptions).

Direct calls to exit() are especially bad if done in libraries as it will doom the library user and it should be a library user's choice to implement some kind of error recovery or not. If you want an example of why calling exit() from a library is bad, it leads for instance people to ask this question.

There is an undisputed legitimate use of exit() as the way to end a child process started by fork() on Operating Systems supporting it. Going back to the code before fork() is usually a bad idea. This is the rationale explaining why functions of the exec() family will never return to the caller.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
kriss
  • 23,497
  • 17
  • 97
  • 116
  • 9
    exit() is not a system call –  Aug 12 '10 at 12:25
  • @anon: you are right, technically it's a return to system, but there is not much difference. – kriss Sep 01 '10 at 11:18
  • @JonathanLeffler In `main()` it's preferred to use `return` or `exit`? Look for exmple [THIS](https://gist.github.com/anonymous/c293fbd346a534912e69) code. – ᴜsᴇʀ Jan 19 '14 at 00:17
  • 5
    I usually use `return` in `main()`. Certainly, I use `return 0;` at the end of `main()` — I sometimes use `exit();` in the body of the function. I don't like the C99 rule about falling off the end of `main()` being equivalent to `return 0;` at the end; it was a silly special case to make (though C++ led the way first in doing the damage). – Jonathan Leffler Jan 19 '14 at 00:20
  • 1
    Note: the C11 standard has additional functions `at_quick_exit()`, `_Exit()` (also in C99), and `quick_exit()`. The `_exit()` function is from POSIX, but is essentially the same as `_Exit()`. – Jonathan Leffler Jan 19 '14 at 01:38
  • if return executes exit then they aren't the same. the result will be the same but if you say they do the same and then say they are calling exit it would resolve to calling exit in main produces an infinite recursion of exit. – dhein Feb 25 '16 at 14:03
  • @Zaibis: read better. I'm not writing that from main exit does a return but that return called from main does an exit. In other words a call to return from main is a going out of the program. Also neither return nor exit are function calls henceforth there can't be any possible issue with recursion. I say it again: *exit is not a function call*, it not even a typical system call, because if it actually jumps somewhere but it won't ever come back from there. – kriss Mar 03 '16 at 17:43
  • 3
    If you wanted an example to illustrate why exit in a library is bad: it makes people ask this question http://stackoverflow.com/q/34043652/168175. And then presumably either use the hack or go with IPC for no good reason – Flexo Sep 06 '16 at 07:29
  • a difference between abort and _exit : abort sends SIGABRT signal so that a core file will be left (if ulimit -c allows for that). So if you want to exit on error with core and without invoking static destructors then abort can be of use. – MichaelMoser Jul 19 '17 at 14:15
  • Could one avoid or resolve the mythification created by writing "both do *(nearly)* the same ...in the main() function"? Specifically, say what is the difference or whether there isn't any, when either is used in main()? – Max Mar 18 '19 at 19:26
  • @Max: as far as I know return in the main function is getting back to the caller of main (internal function not exposed to programmer) which will perform the code necessary to exit the program, while exit will jump to that code, be it called from main or anywhere else. These differences are dependent of the ecosystem (OS, compiler, libs, etc) where you are running your code. In some cases there may no be difference at all and anyway all happens behind the scene for the programmer unless you are writing low level code (ie: they matter if you write exit() implementation). – kriss Mar 19 '19 at 09:45
  • @kriss: thx for the reply. It would be logical that the call to main() would be followed immediately by a call/jump to exit(), because *whatever* would be needed to do in addition to that, exit() should do it anyway. Now I read in other sources that some locally scoped destructors weren't called by exit() in older versions of c++, but this was considered as bug and the difference doesn't exist any more. So, if there are no OS or compiler bugs, a call to exit(0) should be *exactly* equivalent to "return 0" - maybe 2-3 machine instructions faster, and maybe considered "dirty". – Max Mar 30 '19 at 00:52
  • @Max: you should also consider that the program entry point may *not* be main() (actually on Windows it is not and there is a gcc option allowing to change it). From compiler perspective main() is a perfectly normal function with input parameters and return value. It's the caller of main which knows that after the return of main it will exit program. Also recursive calls to main (even indirect recursive call) is not forbidden. Theses cases are very unusual, but changing main() code to perform exactly the same thing that exit does would break them with no added value. – kriss Mar 31 '19 at 02:38
  • 1
    Returning to the OS eventually happens via some kind of system call, but the `exit()` libc function is *not* a simple wrapper for that system call. It has to flush stdio buffers and run `atexit` functions before doing that. On POSIX systems, the true system call is `_exit(2)` or `exit_group(2)`, and the library function is `exit(3)`. See [Syscall implementation of exit()](https://stackoverflow.com/q/46903180) for Linux. Using `_exit()` directly can leave line-buffered `printf` output unwritten (or full-buffered if you redirected to a file). – Peter Cordes Aug 07 '20 at 22:11
  • Indeed @PeterCordes, and the difference betwseen exit() and _exit() is already explained in the answer. But I agree I gave less details than what you get doing `man exit` or `man _exit`. Should I include the complete Unix man in this answer ? – kriss Aug 07 '20 at 22:53
  • No, just avoid mis-statements in the TL:DR part, please. The very start of your answer states that `exit` is a system call. And the first comment tried to correct that, but your reply comment appeared to claim that returning to the OS wasn't a system call. @anon is right, it's not a system call: it does some stuff and *then* makes a system call that terminates the process. I wasn't talking about the later discussion of exit vs. _exit buried deeper in your answer, just the up-front claim that everyone looking at your answer will see, even if they start skimming once they see how long it is. – Peter Cordes Aug 07 '20 at 23:02
  • ISO C says that returning from (the initial call to) main is equivalent to calling `exit()` with that return value, so you can assume that main's caller looks something like `exit(main(argc, argv))`. I just looked at the question in more detail, and realized it's asking about return in general, not just in main, so your answer has to spend more space on that part. Someone linked this Q from [Return vs Exit from main function in C](https://stackoverflow.com/q/63309629) so I had that question in mind when looking at this answer as a possible duplicate. (It's not, and shouldn't try to be.) – Peter Cordes Aug 07 '20 at 23:04
  • @Peter Cordes: actually my comment merely pointed out that while exit() is part of kernel API, telling it's a call is an abuse of language because it never comes back. But ok, it's also doing some additional stuff beforehand. – kriss Aug 08 '20 at 07:21
  • Then your comment was oversimplified to the point of being wrong. The C `exit` function isn't part of the kernel API. The C `exit` function has to do some C stuff first (atexit, and stdio flushing at least) before telling the kernel it wants to exit the process. The kernel API on Linux includes the system call used by the C `_exit` wrapper function, and exit_group. On Windows there's an `ExitProcess` WinAPI function exposed by the DLLs. – Peter Cordes Aug 08 '20 at 07:29
  • In normal C implementations, even `exit_group(0)` doesn't inline a `syscall` instruction, so really it's all libc wrapper functions, but simplifying to calling `_exit()` "a system call" is normal. Those thin wrapper functions only exist to adapt the system-call asm calling convention (like `mov eax, 231` / `syscall` on x86-64 GNU/Linux for an exit_group system call) to the function-calling convention that compilers know how to generate code for. That's why `_exit(2)` and `write(2)` are in a separate section of man pages than full library functions like `exit(3)` and `fwrite(3)`. – Peter Cordes Aug 08 '20 at 07:32
  • My point is, with confusion between `exit(3)` and `_exit(2)` already a possible problem, it doesn't help to call `exit()` "a system call". – Peter Cordes Aug 08 '20 at 07:33
  • @PeterCordes : I rephrased the first sentence to be more technically correct. However I'm not sure it doesn't make the answer more confusing rather than less confusing. – kriss Aug 08 '20 at 22:47
  • If you want to keep that top part simple, you could just say that "`exit()` terminates the whole program, not just the current function. Returning from `main` is like calling exit". Getting into whether it's a library function or system call isn't necessary at that point, IMO. Also, `return` is a statement. I don't think calling it an "instruction" is super helpful; I know you don't mean that as a technical term, but if you are going to call it anything, I'd suggest using accurate terminology. I'll attempt an edit, see if you like it... – Peter Cordes Aug 08 '20 at 22:54
  • Saying that exit() terminates the whole program should be avoided as it makes it confusing with ret from main (which also terminates the whole program, but do it by returning to he caller of main() which will call exit). If after edition the answer hints that calling exit and returning from main is the same thing then the answer has an issue. – kriss Aug 09 '20 at 07:47
  • @PeterCordes: writing statement rather than instruction it mostly harmless, but focus on C syntax has it only means something when opposing statement to expression. Also if you want to be accurate return is not a statement. The statement also includes value returned by return and semicolon, return is merely the keyword introducing the statement. Instruction is not really less accurate as it has no technical meaning in C. – kriss Aug 09 '20 at 07:52
  • You're right, good point that the return keyword isn't a complete statement on its own. Edited again to just drop calling it anything right away, and only later point out that it's a keyword to better support that point. re: exit vs. return from main: the only difference is that return from main invalidates main's locals before `atexit` functions run. ISO C guarantees that they're otherwise equivalent. See [standard quotes](https://stackoverflow.com/q/63309629), and [return statement vs exit() in main()](https://stackoverflow.com/q/461449) for more about main locals. – Peter Cordes Aug 09 '20 at 08:01
  • It's your answer; if you think it needs another edit, I'm not going to revert it. I just edited to show you what I thought it should say, instead of just trying to describe it in comments. – Peter Cordes Aug 09 '20 at 08:02
  • @PeterCodes: return from main actually returns from a function. It makes a big difference as it's totally allowed to recursively call mains. By the way the standard is clear on that: what is equivalent is returning from *the initial call* to main, you can't be much clearer than that. For now I will let the current wording alone and maybe come back to it later. Beside changes, I believe there are still others things to say missing (maybe I should link to other questions for details). – kriss Aug 09 '20 at 11:43
  • *For instance automatic C++ OStream locally created won't be flushed on a call to exit and you may lose some unflushed data ...* <-- What exactly do ***flushed*** and ***unflushed*** mean here? I still get confused in these terms. Thanks in advance! – Milan Dec 17 '20 at 01:27
  • 2
    @Milan: some IO are buffered, this is the cas for OStream. In that context flushed means the data was actually sent to console/written to disk and not just kept in buffer. Not flushed means the data is still in some memory buffers in the application but not yet sent to the system. In C++ docs they call the underlying system object to which data is sent a "controlled sequence". There is an explicit flush method on OStream do do that. – kriss Dec 17 '20 at 03:59
  • @kriss thank you so much for addressing my query. Just to confirm, "some IOs are buffered" -- this means that some IOs have some sort of intermediate volatile memory between program runtime and actual system (non-volatile) memory, right? (I know this might be a very simple question, but I often get confused). So, when it gets flushed, it means that content from that intermediate volatile memory gets copied (& saved) to the actual system (non-volatile) memory, right? Thanks again :) – Milan Dec 18 '20 at 23:17
  • 1
    @Milan: yes, it's basically that. Except system layer is not just about non volatile data, but also data sent to terminal or to network for instance. Both C++ and C have some memory buffer layer (volatile as you call it). In C++ it's OStream. In C it's the IO functions whose name starts with an "f" (like fread, fwrite, fprintf). These buffered data are sent when flushed of when the buffered stream is closed. – kriss Dec 18 '20 at 23:49
25

I wrote two programs:

int main(){return 0;}

and

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

After executing gcc -S -O1. Here what I found watching at assembly (only important parts):

main:
    movl    $0, %eax    /* setting return value */
    ret                 /* return from main */

and

main:
    subq    $8, %rsp    /* reserving some space */
    movl    $0, %edi    /* setting return value */
    call    exit        /* calling exit function */
                        /* magic and machine specific wizardry after this call */

So my conclusion is: use return when you can, and exit() when you need.

hurufu
  • 545
  • 7
  • 10
  • This is a very good answer except for the conclusion; IMHO it motivates the opposite one: we can assume that the `ret` instruction returns to a point where there is maybe some additional work done, but in the very end the exit() function will be called anyway - or how could be avoided to do whatever exit() does? Which means the former does just some additional "duplicate" work without any benefit over the second solution, given that exit() is most certainly called in the very end, anyway. – Max Mar 18 '19 at 19:45
  • 2
    @Max: it is not forbidden to recursively call main from your own program. Henceforth you should not assume that returning from main will immediately exit, this would break code semantic. In some (rare) cases it's even usefull. For instance to help prepare/clear some context before calling main after changing code entry-point to something different from main. This could even be done by some run-time code injection method. Of course when it's your program do whatever you prefer or believe is easier to read. – kriss Apr 03 '19 at 09:13
  • @kriss: this is an excellent point which hasn't been mentioned earlier. Although I think it's extremely rare that main() is called recursively, this possibility clarifies IMHO better than anything else the difference between return and exit(0). – Max Apr 11 '19 at 00:35
11

In C, there's not much difference when used in the startup function of the program (which can be main(), wmain(), _tmain() or the default name used by your compiler).

If you return in main(), control goes back to the _start() function in the C library which originally started your program, which then calls exit() anyways. So it really doesn't matter which one you use.

Dumb Guy
  • 3,336
  • 21
  • 23
  • 3
    It does matter. exit() immediately terminates the program, no matter where it is called. return only exits the current function. the only location they do the same thing is in main() –  Aug 11 '10 at 23:17
  • Thanks, i have fixed the wording. It is not necessarily only in main(), as not all compilers use the same function name for the startup function. – Dumb Guy Aug 11 '10 at 23:23
  • 8
    I guess you write all your programs in one big main function? ;-) – C.J. Aug 12 '10 at 10:41
  • 1
    Answer is not complete but still informative. – Jagdish Jun 26 '15 at 02:56
4

For the most part, there is no difference in a C program between using return and calling exit() to terminate main().

The time when there is a difference is if you've created code that will be executed after you return from main() that relies on variables local to main(). One way that manifests itself is with setvbuf():

int main(void)
{
    char buffer[BUFSIZ];
    setvbuf(stdout, buffer, _IOFBF, BUFSIZ);
    …code using stdout…
    return 0;
}

In this example, the buffer provided via setvbuf() goes out of scope when main() returns, but the code that flushes and closes stdout will be attempting to use that buffer. This leads to undefined behaviour.

The other mechanism is to invoke atexit() with a function that accesses data from main() — via a pointer. This is harder to set up as the functions called via the atexit() mechanism are not given any arguments. So, you have to do something like this:

static void *at_exit_data = 0;

static void at_exit_handler(void)
{
    char *str = at_exit_data;
    printf("Exiting: %s\n", str);
}

int main(void);
{
    char buffer[] = "Message to be printed via functions registered with at_exit()";
    at_exit_data = buffer;
    at_exit(at_exit_handler);
    …processing…
    return 0;
}

Again, the buffer pointed at by at_exit_data has ceased to exist when the program returned from main() and so the handler function invokes undefined behaviour.

There is a related function, at_quick_exit(), but the functions registered with it are only called if the quick_exit() function is called, which precludes the functions being called after main() returns.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
1

the return statement exits from the current function and exit() exits from the program

they are the same when used in main() function

also return is a statement while exit() is a function which requires stdlb.h header file

kapil
  • 625
  • 12
  • 20
  • They're the same *if* main is called by any any of the functions *in* your program. That's rare; most programs don't use a recursive or re-entrant main, but if we're talking about language details then that's a possibility. – Peter Cordes Jun 12 '21 at 15:23