In der C-Programmierung gibt es zwei Möglichkeiten, ein Programm über die Hauptfunktion zu beenden: mit Return und mit Exit().
int main() { printf("Hello, World!"); return 0; // Method 1: Normal termination } int main() { printf("Hello, World!"); exit(0); // Method 2:Normal termination }
Warum können beide Methoden das Programm korrekt beenden, obwohl sie völlig unterschiedlich aussehen?
In diesem Artikel werden wir dieses Rätsel lösen, indem wir verstehen, wie C-Programme tatsächlich starten und beenden.
Beachten Sie, dass sich dieser Artikel auf die Implementierung in GNU/Linux-Umgebungen konzentriert, insbesondere auf die Verwendung von glibc.
Lassen Sie uns zunächst untersuchen, wie die Exit-Funktion funktioniert, um den Programmbeendigungsmechanismus zu verstehen.
Die Exit-Funktion ist eine Standardbibliotheksfunktion, die ein Programm ordnungsgemäß beendet.
Intern ist die Funktion _exit, die von Exit aufgerufen wird, in Glibc wie folgt implementiert:
void _exit (int status) { while (1) { INLINE_SYSCALL (exit_group, 1, status); #ifdef ABORT_INSTRUCTION ABORT_INSTRUCTION; #endif } }
Wenn wir uns diese Implementierung ansehen, können wir sehen, dass die _exit-Funktion einen Exit-Status als Argument erhält und exit_group (Systemaufrufnummer 231) aufruft.
Dieser Systemaufruf führt die folgenden Vorgänge aus:
Durch diese Vorgänge wird das Programm ordnungsgemäß beendet.
Warum beendet die Rückkehr von main() das Programm auch ordnungsgemäß?
Um dies zu verstehen, müssen wir eine wichtige Tatsache wissen: C-Programme beginnen eigentlich nicht im Hauptprogramm.
Überprüfen wir die Standardeinstellungen des Linkers (ld), um den tatsächlichen Einstiegspunkt zu sehen:
$ ld --verbose | grep "ENTRY" ENTRY(_start)
Wie diese Ausgabe zeigt, ist der eigentliche Einstiegspunkt eines C-Programms die _start-Funktion. main wird nach _start.
aufgerufen
Die _start-Funktion ist in der Standardbibliothek implementiert und in glibc sieht sie so aus:
_start: # Initialize stack pointer xorl %ebp, %ebp popq %rsi # Get argc movq %rsp, %rdx # Get argv # Setup arguments for main pushq %rsi # Push argc pushq %rdx # Push argv # Call __libc_start_main call __libc_start_main
Die Funktion _start hat zwei Hauptaufgaben:
Nachdem diese Initialisierungen abgeschlossen sind, wird __libc_start_main aufgerufen.
Diese Funktion ist für den Aufruf der Hauptfunktion verantwortlich.
Lassen Sie uns nun im Detail untersuchen, wie __libc_start_main funktioniert.
__libc_start_call_main, das von __libc_start_main aufgerufen wird, wird wie folgt implementiert:
_Noreturn static void __libc_start_call_main (int (*main) (int, char **, char ** MAIN_AUXVEC_DECL), int argc, char **argv #ifdef LIBC_START_MAIN_AUXVEC_ARG , ElfW(auxv_t) *auxvec #endif ) { int result; /* Memory for the cancellation buffer. */ struct pthread_unwind_buf unwind_buf; int not_first_call; DIAG_PUSH_NEEDS_COMMENT; #if __GNUC_PREREQ (7, 0) /* This call results in a -Wstringop-overflow warning because struct pthread_unwind_buf is smaller than jmp_buf. setjmp and longjmp do not use anything beyond the common prefix (they never access the saved signal mask), so that is a false positive. */ DIAG_IGNORE_NEEDS_COMMENT (11, "-Wstringop-overflow="); #endif not_first_call = setjmp ((struct __jmp_buf_tag *) unwind_buf.cancel_jmp_buf); DIAG_POP_NEEDS_COMMENT; if (__glibc_likely (! not_first_call)) { struct pthread *self = THREAD_SELF; /* Store old info. */ unwind_buf.priv.data.prev = THREAD_GETMEM (self, cleanup_jmp_buf); unwind_buf.priv.data.cleanup = THREAD_GETMEM (self, cleanup); /* Store the new cleanup handler info. */ THREAD_SETMEM (self, cleanup_jmp_buf, &unwind_buf); /* Run the program. */ result = main (argc, argv, __environ MAIN_AUXVEC_PARAM); } else { /* Remove the thread-local data. */ __nptl_deallocate_tsd (); /* One less thread. Decrement the counter. If it is zero we terminate the entire process. */ result = 0; if (atomic_fetch_add_relaxed (&__nptl_nthreads, -1) != 1) /* Not much left to do but to exit the thread, not the process. */ while (1) INTERNAL_SYSCALL_CALL (exit, 0); } exit (result); }
Bei dieser Implementierung sind die wichtigsten Teile, auf die man sich konzentrieren sollte, wie folgt:
result = main (argc, argv, __environ MAIN_AUXVEC_PARAM); exit(result);
Hier kommt es darauf an, wie die Hauptfunktion ausgeführt wird und wie ihr Rückgabewert behandelt wird:
Durch diesen Mechanismus:
In beiden Fällen wird letztendlich „exit“ aufgerufen, um eine ordnungsgemäße Beendigung des Programms sicherzustellen.
C-Programme verfügen über den folgenden Mechanismus:
Durch diesen Mechanismus:
Beachten Sie, dass dieser Mechanismus nicht auf GNU/Linux beschränkt ist; Ähnliche Implementierungen gibt es in anderen Betriebssystemen (wie Windows und macOS) und verschiedenen C-Standardbibliotheken.
Das obige ist der detaillierte Inhalt vonWarum sowohl return als auch exit() in main() funktionieren. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!