C語(yǔ)言中這么騷的退出程序的方式你知道幾個(gè)?
前言
在本篇文章當(dāng)中主要給大家介紹C語(yǔ)言當(dāng)中一些不常用的特性,比如在main
函數(shù)之前和之后設(shè)置我們想要執(zhí)行的函數(shù),以及各種花式退出程序的方式。
main函數(shù)是最先執(zhí)行和最后執(zhí)行的函數(shù)嗎?
C語(yǔ)言構(gòu)造和析構(gòu)函數(shù)
通常我們?cè)趯?xiě)C程序的時(shí)候都是從main
函數(shù)開(kāi)始寫(xiě),因此我們可能沒(méi)人有關(guān)心過(guò)這個(gè)問(wèn)題,事實(shí)上是main函數(shù)不是程序第一個(gè)執(zhí)行的函數(shù),也不是程序最后一個(gè)執(zhí)行的函數(shù)。
void __attribute__((constructor)) init1() {printf("before main funciton\n");}int main() {printf("this is main funciton\n");}
我們編譯上面的代碼然后執(zhí)行,輸出結(jié)果如下圖所示:
? ?code git:(main) ./init.outbefore main funcitonthis is main funciton
由此可見(jiàn)main函數(shù)并不是第一個(gè)被執(zhí)行的函數(shù),那么程序第一次執(zhí)行的函數(shù)是什么呢?很簡(jiǎn)單我們看一下程序的調(diào)用棧即可。

從上面的結(jié)果可以知道,程序第一個(gè)執(zhí)行的函數(shù)是_start
,這是在類(lèi)Unix操作系統(tǒng)上執(zhí)行的第一個(gè)函數(shù)。
那么main函數(shù)是程序執(zhí)行的最后一個(gè)函數(shù)嗎?我們看下面的代碼:
void __attribute__((destructor)) __exit() {printf("this is exit\n");}void __attribute__((constructor)) init() {printf("this is init\n");}int main() {printf("this is main\n");return 0;}
上面程序的輸出結(jié)果如下:
? ?code git:(main) ./out.outthis is initthis is mainthis is exit
由此可見(jiàn)main函數(shù)也不是我們最后執(zhí)行的函數(shù)!事實(shí)上我們除了上面的方法之外我們也可以在libc當(dāng)中注冊(cè)一些函數(shù),讓程序在main函數(shù)之后,退出執(zhí)行前執(zhí)行這些函數(shù)。
on_exit和atexit函數(shù)
我們可以使用上面兩個(gè)函數(shù)進(jìn)行函數(shù)的注冊(cè),讓程序退出之前執(zhí)行我們指定的函數(shù)
void __attribute__((destructor)) __exit() {printf("this is exit\n");}void __attribute__((constructor)) init() {printf("this is init\n");}void on__exit() {printf("this in on exit\n");}void at__exit() {printf("this in at exit\n");}int main() {on_exit(on__exit, NULL);atexit(at__exit);printf("this is main\n");return 0;}
this is initthis is mainthis in at exitthis in on exitthis is exit
我們可以仔細(xì)分析一下上面程序執(zhí)行的順序。首先是執(zhí)構(gòu)造函數(shù),然后執(zhí)行 atexit 注冊(cè)的函數(shù),再執(zhí)行 on_exit 注冊(cè)的函數(shù),最后執(zhí)行析構(gòu)函數(shù)。從上面程序的輸出我們可以知道我們注冊(cè)的函數(shù)生效了,但是需要注意一個(gè)問(wèn)題,先注冊(cè)的函數(shù)后執(zhí)行,不管是使用 atexit 還是 on_exit 函數(shù)。我們現(xiàn)在看下面的代碼:
void __attribute__((destructor)) __exit() {printf("this is exit\n");}void __attribute__((constructor)) init() {printf("this is init\n");}void on__exit() {printf("this in on exit\n");}void at__exit() {printf("this in at exit\n");}int main() {// 調(diào)換下面兩行的順序atexit(at__exit);on_exit(on__exit, NULL);printf("this is main\n");return 0;}
上面的代碼輸出如下:
this is initthis is mainthis in on exitthis in at exitthis is exit
從輸出的結(jié)果看確實(shí)和上面我們提到的規(guī)則一樣,先注冊(cè)的函數(shù)后執(zhí)行。這一點(diǎn)再linux程序員開(kāi)發(fā)手冊(cè)里面也提到了。

但是這里有一點(diǎn)需要注意的是我們應(yīng)該盡可能使用atexit函數(shù),而不是使用on_exit函數(shù),因?yàn)閍texit函數(shù)是標(biāo)準(zhǔn)規(guī)定的,而on_exit并不是標(biāo)準(zhǔn)規(guī)定的。
exit和_exit函數(shù)
其中exit函數(shù)是libc給我們提供的函數(shù),我們可以使用這個(gè)函數(shù)正常的終止程序的執(zhí)行,而且我們?cè)谇懊孀?cè)的函數(shù)還是能夠被執(zhí)行。比如在下面的代碼當(dāng)中:
void __attribute__((destructor)) __exit1() {printf("this is exit1\n");}void __attribute__((destructor)) __exit2() {printf("this is exit2\n");}void __attribute__((constructor)) init1() {printf("this is init1\n");}void __attribute__((constructor)) init2() {printf("this is init2\n");}void on__exit1() {printf("this in on exit1\n");}void at__exit1() {printf("this in at exit1\n");}void on__exit2() {printf("this in on exit2\n");}void at__exit2() {printf("this in at exit2\n");}int main() {// _exit(1);on_exit(on__exit1, NULL);on_exit(on__exit2, NULL);atexit(at__exit1);atexit(at__exit2);printf("this is main\n");exit(1);return 0;}
上面的函數(shù)執(zhí)行結(jié)果如下所示:
this is init1this is init2this is mainthis in at exit2this in at exit1this in on exit2this in on exit1this is exit2this is exit1
可以看到我們的代碼被正常執(zhí)行啦。
但是_exit是一個(gè)系統(tǒng)調(diào)用,當(dāng)執(zhí)行這個(gè)方法的時(shí)候程序會(huì)被直接終止,我們看下面的代碼:
void __attribute__((destructor)) __exit1() {printf("this is exit1\n");}void __attribute__((destructor)) __exit2() {printf("this is exit2\n");}void __attribute__((constructor)) init1() {printf("this is init1\n");}void __attribute__((constructor)) init2() {printf("this is init2\n");}void on__exit1() {printf("this in on exit1\n");}void at__exit1() {printf("this in at exit1\n");}void on__exit2() {printf("this in on exit2\n");}void at__exit2() {printf("this in at exit2\n");}int main() {// _exit(1);on_exit(on__exit1, NULL);on_exit(on__exit2, NULL);atexit(at__exit1);atexit(at__exit2);printf("this is main\n");_exit(1); // 只改了這個(gè)函數(shù) 從 exit 變成 _exitreturn 0;}
上面的代碼輸出結(jié)果如下所示:
this is init1this is init2this is main
可以看到我們注冊(cè)的函數(shù)和最終的析構(gòu)函數(shù)都沒(méi)有被執(zhí)行,程序直接退出啦。
花式退出
出了上面的_exit
函數(shù)之外,我們還可以使用其他的方式直接退出程序:
void __attribute__((destructor)) __exit1() {printf("this is exit1\n");}void __attribute__((destructor)) __exit2() {printf("this is exit2\n");}void __attribute__((constructor)) init1() {printf("this is init1\n");}void __attribute__((constructor)) init2() {printf("this is init2\n");}void on__exit1() {printf("this in on exit1\n");}void at__exit1() {printf("this in at exit1\n");}void on__exit2() {printf("this in on exit2\n");}void at__exit2() {printf("this in at exit2\n");}int main() {// _exit(1);on_exit(on__exit1, NULL);on_exit(on__exit2, NULL);atexit(at__exit1);atexit(at__exit2);printf("this is main\n");syscall(SYS_exit, 1); // 和 _exit 效果一樣return 0;}
出了上面直接調(diào)用函數(shù)的方法退出函數(shù),我們還可以使用內(nèi)聯(lián)匯編退出函數(shù),比如在64位操作系統(tǒng)我們可以使用下面的代碼退出程序:
include <stdio.h>include <stdlib.h>include <unistd.h>include <sys/syscall.h> void __attribute__((destructor)) __exit1() {printf("this is exit1\n");}void __attribute__((destructor)) __exit2() {printf("this is exit2\n");}void __attribute__((constructor)) init1() {printf("this is init1\n");}void __attribute__((constructor)) init2() {printf("this is init2\n");}void on__exit1() {printf("this in on exit1\n");}void at__exit1() {printf("this in at exit1\n");}void on__exit2() {printf("this in on exit2\n");}void at__exit2() {printf("this in at exit2\n");}int main() {// _exit(1);on_exit(on__exit1, NULL);on_exit(on__exit2, NULL);atexit(at__exit1);atexit(at__exit2);printf("this is main\n");asm("movq $60, %%rax;""movq $1, %%rdi;""syscall;":::"eax");return 0;}
上面是在64位操作系統(tǒng)退出程序的匯編實(shí)現(xiàn),在64為系統(tǒng)上退出程序的系統(tǒng)調(diào)用號(hào)為60。下面我們使用32位操作系統(tǒng)上的匯編實(shí)現(xiàn)程序退出,在32位系統(tǒng)上退出程序的系統(tǒng)調(diào)用號(hào)等于1:
void __attribute__((destructor)) __exit1() {printf("this is exit1\n");}void __attribute__((destructor)) __exit2() {printf("this is exit2\n");}void __attribute__((constructor)) init1() {printf("this is init1\n");}void __attribute__((constructor)) init2() {printf("this is init2\n");}void on__exit1() {printf("this in on exit1\n");}void at__exit1() {printf("this in at exit1\n");}void on__exit2() {printf("this in on exit2\n");}void at__exit2() {printf("this in at exit2\n");}int main() {// _exit(1);on_exit(on__exit1, NULL);on_exit(on__exit2, NULL);atexit(at__exit1);atexit(at__exit2);printf("this is main\n");asm volatile("movl $1, %%eax;""movl $1, %%edi;""int $0x80;":::"eax");return 0;}
總結(jié)
在本篇文章當(dāng)中主要給大家介紹C語(yǔ)言當(dāng)中一些與程序退出的騷操作,希望大家有所收獲!