天天看點

UNIX環境進階程式設計(第7章 程序環境)

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函數直接終止程序而不傳回到啟動例程。

UNIX環境進階程式設計(第7章 程式環境)

    核心執行程式的唯一方法是調用一個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)指令可檢視目前所有的環境變量。

UNIX環境進階程式設計(第7章 程式環境)

    通路或設定環境變量不能直接使用全局變量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)堆:通常在堆中進行動态配置設定。

UNIX環境進階程式設計(第7章 程式環境)

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配置設定空間。