C程序的啓動過程
我們通常認爲C語言的起始函數是main函數,實際上一個程序的啓動函數不一定是main函數,這個可以採用鏈接器來設置,但是gcc中默認main函數就是C語言的入口函數,在執行main函數之前,內核會啓動一個特殊例程。
這個特殊例程的作用:
- 蒐集命令行的參數傳遞給main函數中的argc和argv;
- 蒐集環境信息構建環境表並傳遞給main函數;
- 登記進程的終止函數;
進程終止函數atexit()
#include <stdlib.h>
int atexit(void(*function)(void));
返回:成功返回0,出錯返回-1;
功能:註冊終止函數(即main執行結束後調用的函數,當程序通過調用exit()或從main中返回時,參數function指定的函數會先被 調用,然後才真正由exit()結束程序)
- 每個啓動的進程都默認登記了一個標準的終止函數;
- 終止函數在進程終止時釋放進程所佔用的一些資源;
- 登記的多個終止函數執行順序是以棧的方式執行,先登記的後執行;
進程終止方式
a.正常終止
- 從main函數返回;
- 調用exit(標準c庫函數);
- 調用_exit或_Exit(系統調用)
- 調用abort;
- 接收到一個信號並終止;
- 最後一個線程對其取消請求做處理響應;
- 通常程序運行成功返回0,失敗返回非0;
- 在shell中可以查看進程返回值(echo $?);
一個問題:main函數退出之後,是否還可以執行程序?
#include
int atexit(void(*func)(void));
其中,atexit的參數是一個地址,當調用此函數時無需傳遞任何參數,該函數也無返回值,atexit函數稱爲終止處理程序註冊程序,註冊完成以後,當函數終止時,exit()函數會主動調用前面的各個函數。由於exit是在main函數調用結束以後調用,所以這些函數的執行肯定在main函數之後。這就是上面問題的答案。即採用atexit函數登記相關的執行函數即可。
簡單的示例:
#include <stdlib.h>
#include <stdio.h>
void func1(void)
{
printf("in func1\n");
}
void func2(void)
{
printf("in func2\n");
}
void func3(void)
{
printf("in func3\n");
}
int main(void)
{
atexit(func1);
atexit(func2);
atexit(func3);
printf("I'm main\n");
exit(0);
//return 0;
}
具體執行結果如下所示:
根據exit的執行過程可知,exit首先會調用各個終止處理程序,然後按需多次調用close(),關閉所有打開流,也就是說exit函數會執行一個標準庫的清理關閉操作,對所有打開的流調用fclose(),這樣就會造成所有緩衝的輸出數據都被沖洗寫入文件中。
注意:在終止方式方式中,調用_exit,_Exit都不會調用終止程序,異常終止也不會。
如果將上述程序中最後的exit(0)改爲_exit(0)或_Exit(0),執行結果如下:
也就是說根本不會執行到最開始註冊的函數。