Dalam pengaturcaraan C, terdapat dua cara untuk menamatkan program daripada fungsi utama: menggunakan return dan menggunakan exit().
int main() { printf("Hello, World!"); return 0; // Method 1: Normal termination } int main() { printf("Hello, World!"); exit(0); // Method 2:Normal termination }
Mengapakah kedua-dua kaedah boleh menamatkan program dengan betul, walaupun ia kelihatan berbeza?
Dalam artikel ini, kami akan membongkar misteri ini dengan memahami cara program C sebenarnya bermula dan ditamatkan.
Harap maklum bahawa artikel ini memfokuskan pada pelaksanaan dalam persekitaran GNU/Linux, khususnya menggunakan glibc.
Pertama, mari kita periksa cara fungsi keluar berfungsi untuk memahami mekanisme penamatan program.
Fungsi keluar ialah fungsi perpustakaan standard yang menamatkan program dengan betul.
Secara dalaman, fungsi _exit, yang dipanggil melalui exit, dilaksanakan dalam glibc seperti berikut:
void _exit (int status) { while (1) { INLINE_SYSCALL (exit_group, 1, status); #ifdef ABORT_INSTRUCTION ABORT_INSTRUCTION; #endif } }
Melihat pelaksanaan ini, kita dapat melihat bahawa fungsi _exit menerima status keluar sebagai hujahnya dan memanggil exit_group (nombor panggilan sistem 231).
Panggilan sistem ini melaksanakan operasi berikut:
Melalui operasi ini, program ditamatkan dengan betul.
Jadi, mengapakah kembali dari main() juga menamatkan program dengan betul?
Untuk memahami perkara ini, kita perlu mengetahui fakta penting: program C sebenarnya tidak bermula dari utama.
Mari kita semak tetapan lalai pemaut (ld) untuk melihat titik masuk sebenar:
$ ld --verbose | grep "ENTRY" ENTRY(_start)
Seperti yang ditunjukkan oleh output ini, titik kemasukan sebenar program C ialah fungsi _start. utama dipanggil selepas _start.
Fungsi _start dilaksanakan dalam perpustakaan standard, dan dalam glibc, ia kelihatan seperti ini:
_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
Fungsi _start mempunyai dua peranan utama:
Selepas permulaan ini selesai, __libc_start_main dipanggil.
Fungsi ini bertanggungjawab untuk memanggil fungsi utama.
Sekarang, mari kita periksa cara __libc_start_main berfungsi secara terperinci.
__libc_start_call_main, yang dipanggil oleh __libc_start_main, dilaksanakan seperti berikut:
_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); }
Dalam pelaksanaan ini, bahagian utama yang perlu diberi tumpuan adalah seperti berikut:
result = main (argc, argv, __environ MAIN_AUXVEC_PARAM); exit(result);
Di sini, perkara penting ialah bagaimana fungsi utama dilaksanakan dan nilai pulangannya dikendalikan:
Melalui mekanisme ini:
Dalam mana-mana kes, keluar akhirnya dipanggil, memastikan penamatan program yang betul.
Program C mempunyai mekanisme berikut:
Melalui mekanisme ini:
Perhatikan bahawa mekanisme ini tidak terhad kepada GNU/Linux; pelaksanaan serupa wujud dalam sistem pengendalian lain (seperti Windows dan macOS) dan pustaka standard C yang berbeza.
Atas ialah kandungan terperinci Mengapa Kedua-dua return dan exit() Berfungsi dalam main(). Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!