1 C程式的啟動與終止
C程式總是從main函數開始執行。main函數的原型:intmain(int argc, char *argv[]);當核心執行C程式時(使用一個exec函數),在調用main函數前先調用一個特殊的啟動例程。可執行程式檔案将此啟動例程指定為程式的起始位址。啟動例程從核心取得指令行參數和環境變量值,然後為按上述方式調用main函數做好安排。
啟動例程一般是用彙編語言實作,作用是使得從main函數傳回後立即調用exit函數。等價代碼為:
exit(main(argc,argv));
1)main函數是被啟動例程調用,故從main函數return時,仍傳回到啟動例程中,main函數的傳回值被啟動例程得到,并作為參數調用exit函數。
2)也可以在main函數中調用exit函數直接終止程序而不傳回到啟動例程。
核心執行程式的唯一方法是調用一個exec函數。程序自願終止的唯一方法是顯式或隐式地(通過調用exit)調用_exit或_Exit。程序也可非自願地由一個信号使其終止。
程序終止的方式有8種,其中5種為正常終止:
1)從main傳回,即return
2)調用exit
3)調用_exit或_Exit
4)最後一個線程從其啟動例程傳回
5)最後一個線程調用pthread_exit
異常終止的3種方式:
6)調用abort
7)接到一個信号并終止
8)最後一個線程對取消請求做出響應
1.1 exit函數
#include<stdlib.h> void exit(int status); void _Exit(int status); #include<unistd.h> void _exit(int status); 說明: 1) 參數:int status是終止狀态 2) 功能:_exit和_Exit立即進入核心,exit則先執行一些清理處理(包括調用執行各終止處理程式,關閉所有标準I/O流等),然後進入核心。 |
1.2 atexit函數
exit函數的功能在進入核心前,會自動調用執行終止處理程式。一個程序最多可以登記32個終止處理程式(終止處理程式即是函數),可通過調用atexit函數來登記這些函數。
#include <stdlib.h> int atexit(void (*func)(void)); //成功傳回0,出錯傳回非0 |
exit調用這些func函數的順序與它們登記時候的順序相反。
#include "apue.h"
static void my_exit1(void);
static void my_exit2(void);
int main(void)
{
if (atexit(my_exit2) != 0)
err_sys("can't register my_exit2");
if (atexit(my_exit1) != 0)
err_sys("can't register my_exit1");
printf("main is done\n");
return 0;
}
static void my_exit1(void)
{
printf("first exit handler\n");
}
static void my_exit2(void)
{
printf("second exit handler\n");
}
[root]# ./a.out
main is done
first exit handler
second exit handler
2參數變量與環境變量
在曆史上,大多數UNIX系統支援main函數帶有3個參數,其中第二個參數是參數表,第三個參數是環境表。參數表與環境表都是字元指針數組。
int main(intargc, char *argv[], char *envp[]);
對于第三個參數環境表envp,全局變量environ包含了改指針數組的位址:
extern char**environ;
因為ISO C規定main函數隻有兩個參數,而且第三個參數與全局變量environ相比也沒用帶來更多的益處,是以POSIX.1也規定應使用environ而不使用第三個參數。
2.1指令行參數
#include "apue.h"
int main(int argc, char *argv[])
{
int i;
for (i = 0; i < argc; i++)
{
printf("argv[%d]: %s\n", i, argv[i]);
}
exit(0);
}
[root]# ./a.out arg1 TEST foo
argv[0]: ./a.out
argv[1]: arg1
argv[2]: TEST
argv[3]: foo
2.2環境變量
通過export(1)指令可檢視目前所有的環境變量。
通路或設定環境變量不能直接使用全局變量environ,需要通過特定的函數:
#include<stdlib.h> char *getenv(const char *name); 傳回值:指向與name關聯的value的指針,若未找到則傳回NULL |
#include<stdlib.h> int putenv(char *str); int setenv(const char *name, const char *value, int rewrite); int unsetenv(const char *name); 傳回值:若成功傳回0,若出錯傳回非0值。 |
3 C程式的存儲空間布局
32位作業系統環境下,當建立一個程序時,都會為該程序配置設定一個4GB大小的虛拟程序位址空間。該空間主要分為以下幾個部分:
1)正文段:由CPU執行的機器指令部分。
2)初始化資料段:即資料段,包含了程式中需明确地賦初值的變量。任何函數外的指派聲明,如int maxcount = 99;
3)未初始化資料段:即bss段,在程式開始執行之前,核心将此段中的資料初始化為0或空指針。任何函數外的未指派的聲明,如long sum[1000];
4)棧:自動變量以及每次函數調用時所需儲存的資訊都存放在此段中。
5)堆:通常在堆中進行動态配置設定。
4函數間跳轉
C語言的goto語句能在一個函數内實作跳轉功能,但要在函數間跳轉——即在棧上跳過若幹調用幀、傳回到目前函數調用路徑上的某一個函數中,則需要使用setjmp和longjmp函數。
頭檔案 | #include <setjmp.h> |
函數原型 | int setjmp(jmp_buf env); |
參數 | env:某種形式的數組,其中存放在調用longjmp時能用來恢複棧狀态的所有資訊。因需要在另一個函數中引用env變量,故需将env變量定義為全局變量。 |
傳回 | 若直接調用則傳回0,若從longjmp調用傳回則傳回非0值。 |
功能 | 在希望傳回到的位置調用setjmp |
頭檔案 | #include <setjmp.h> |
函數原型 | void longjmp(jmp_buf env, int val); |
參數 | env:在調用setjmp時所用的env val:具有非0值,将是從setjmp處傳回的值 |
傳回 | 若直接調用則傳回0,若從longjmp調用傳回則傳回非0值。 |
功能 | 傳回到調用setjmp的位置 |
4.1自動、寄存器和易失變量
#include "apue.h"
#include <setjmp.h>
static void f1(int, int, int, int);
static void f2(void);
static jmp_buf jmpbuffer;
static int globval;
int main(void)
{
int autoval;/*自動變量*/
register int regival;/*寄存器變量*/
volatile int volaval;/*易失變量*/
static int statval;/*靜态變量*/
globval = 1; autoval = 2; regival = 3; volaval = 4; statval = 5;
if (setjmp(jmpbuffer) != 0)
{
printf("after longjmp:\n");
printf("globval = %d, autoval = %d, regiva = %d, "
" volaval = %d, statval = %d\n",
globval , autoval, regival, volaval, statval);
exit(0);
}
globval = 95; autoval = 96; regival = 97; volaval = 98; statval = 99;
f1(autoval, regival, volaval, statval);
exit(0);
}
static void f1(int i, int j, int k, int l)
{
printf("in f1(): \n");
printf("globval = %d, autoval = %d, regiva = %d, "
" volaval = %d, statval = %d\n",
globval, i , j, k, l);
f2();
}
static void f2(void)
{
longjmp(jmpbuffer, 1);
}
[root]# gcc testjmp.c /*不進行任何優化的編譯*/
[root]# ./a.out
in f1():
globval = 95, autoval = 96, regiva = 97, volaval = 98, statval = 99
after longjmp:
globval = 95, autoval = 96, regiva = 97, volaval = 98, statval = 99
[root]# gcc -O testjmp.c /*進行全部優化的編譯*/
[root]# ./a.out
in f1():
globval = 95, autoval = 96, regiva = 97, volaval = 98, statval = 99
after longjmp:
globval = 95, autoval = 2, regiva = 3, volaval = 98, statval = 99
4.2自動變量的潛在問題
聲明自動變量的函數已經傳回後,不能再引用這些自動變量。
#include <stdio.h>
#define DATAFILE "datafile"
FILE *open_data(void)
{
FILE *fp;
char databuf[BUFSIZ];
if ((fp = fopen(DATAFILE, "r")) == NULL)
return NULL;
if (setvbuf(fp, databuf, _IOLBF, BUFSIZ) != 0)
return NULL;
return fp;
}
問題:當open_data傳回時,它在棧上所使用的空間将由下一個被調用函數的棧幀使用。但是,标準I/O庫函數仍将使用其流緩沖區的存儲空間。這就産生了沖突和混亂。
解決:在全局存儲空間靜态地(如static或extern)或者動态地(malloc)為數組databuf配置設定空間。