apue第七章學習總結

apue第七章學習總結

1.main函數

程序是如何執行有關的c程序的?linux

C程序老是從main函數開始執行。main函數的原型是程序員

int main(int argc,char *argv[]);

其中,argc是命令行參數的數目,argv是指向參數的各個指針所構成的數組。shell

當內核執行C程序時(使用一個exec函數),在調用main前先調用一個特殊的啓動例程。可執行程序文件將此啓動例程指定爲程序的起始地址——這是由鏈接編輯器設置的,而鏈接編輯器則由C編譯器(一般是cc)調用。啓動例程從內核取得命令行參數和環境變量值,而後爲按上述方式調用main函數作好安排。數組

2.進程終止

有8種方式使進程終止,其中5種爲正常終止,它們是:編輯器

  • (1).從main返回。
  • (2).調用exit
  • (3).調用_exit_Exit
  • (4).最後一個線程從其啓動例程返回。
  • (5).最後一個線程調用pthread_exit

異常終止有3種方式,它們是:函數

  • (6).調用abort;
  • (7).接到一個信號並終止。
  • (8).最後一個線程對取消請求作出響應。

3.C程序的存儲空間佈局

從歷史上講,C程序一直由下面幾部分組成:工具

  • 正文段。這是由CPU執行的機器指令部分。一般,正文段是可共享的,因此即便是頻繁執行的程序(如文本編輯器,C編譯器和shell等)在存儲器中也只需有一個副本,另外,正文段經常是隻讀的,以防止程序因爲意外而修改其自身的指令。
  • 初始化數據段。一般將此稱爲數據段,它包含了程序中需明確地賦初值的變量。例如,C程序中出如今任何函數以外的聲明:
int maxcount = 99;
  • 非初始化數據段。一般將此段稱爲bss段,這一名稱來源於一個早期的彙編運算符,意思是「block started by symbol"(由符號開始的塊),在程序開始執行以前,內核將此段中的數據初始化爲0或空指針。出如今任何函數外的C聲明
long sum[1000];
  • 棧。自動變量以及每次函數調用時所需保存的信息都存放在此段中。每次調用函數時,其返回地址以及調用者的環境變量(例如某些機器寄存器的值)都存放在棧中。而後,最近被調用的函數在棧上爲其自動和臨時變量分配存儲空間。經過以這種方式使用棧,能夠遞歸調用C函數。遞歸函數每次調用自身時,就使用一個新的棧幀,所以一個函數調用實例中的變量集不會影響另外一個函數調用實例中的變量。
  • 堆。一般在堆中進行動態存儲分配。因爲歷史上造成的慣例,堆位於非初始化數據段和棧之間。

4.環境變量和環境表

環境字符串的形式一般以下:佈局

name=value

環境表(指向實際name=value字符串的指針數組)和環境字符串一般存放在進程存儲空間的頂部(棧之上)。學習

5.setjmp和longjmp函數

在C中,goto語句是不能跨越函數的,而執行這類跳轉功能的是函數setjmplongjmp。可是記住,對於longjmp,大多數實現並不回滾這些自動變量寄存器變量的值。命令行

6.getrlimit和setrlimit函數

每一個進程都有一組資源限制,其中一些能夠用getrlimitsetrlimit函數查詢和更改。

7.習題:

7.1.在Intel x86系統上,不管使用FreeBSD或Linux,若是執行一個輸出"hello world"但不調用exitreturn的程序,則程序的返回代碼爲13(用shell來grep檢查),解釋其緣由。

緣由在於printf的返回值(輸出的字符數)變成了main函數的返回值。固然,並非全部的系統都會出現該狀況。

7.2.到底printf函數的結果什麼時候纔會被真正輸出?

當程序處於交互運行方式時,標準輸出一般處於行緩衝方式,因此當輸出換行符時,上次的結果才被真正輸出。若是標準輸出被定向到一個文件而處於徹底緩衝方式,則當標準I/O清理操做執行時,結果才真正被輸出。

7.3.是否有方法不使用(a).參數傳遞 (b).全局變量這兩種方法,將main中的參數argc和argv傳遞給它所調用的其它函數?

因爲 argcargv的副本不像environ 同樣保存在全局變量中,因此大多數UNIX系統中沒有辦法實現。(而有關environ環境變量的話,能夠經過函數getenv來調用,由於命令行參數和環境變量位於棧之上的高地址)

7.4.在有些UNIX系統中執行程序時訪問不到其數據段的單元0(即數據段的起始地址0存儲的數據),這是一種有意的安排,爲何?

緣由在於當程序上對一個空指針(地址爲NULL,即0)進行解引用操做時,能夠因爲訪問不到想要的地址而終止程序,防止程序訪問到意想不到的數據,在程序開發中,若是指針指向或者操做了意想不到的數據都會視爲危險的行爲。

關於空指針的解引用,在網上找到:

空指針解引用是否致使異常應該是硬件設備和OS組合決定的。之前在VXwork下工做,空指針也能夠解引用,能夠訪問內存0地址,還能夠修改內容。這種狀況下,爲了便於程序員debug,印象中咱們大概是採用了對於0地址內容監控,若是內容有改動則報告或者crash。

7.5.用C語言的typedef工具爲終止處理程序定義一個新數據類型Exitfunc,使用該類型修改atexit的原型。

咱們先看一下atexit的原型:

#include <stdlib.h>
int atexit(void (*func)(void));

因此經過typedef定義的新的數據類型Exitfunc以下:

typedef void Exitfunc(void);  //定義一個數據類型Exitfunc,指向函數void func(void)的函數入口
int atexit(Exitfunc *func);  //改寫後的atexit的原型
//註上面的typedef解釋以下:
Exitfunc * ptr;  //至關於 void (*ptr)(void);  //ptr指向函數的入口

另外對於typedef的使用,有以下介紹:

用途一:定義一種類型的別名,而不僅是簡單的宏替換。

能夠用做同時聲明指針型的多個對象。好比:

char* pa, pb; // 這多數不符合咱們的意圖,它只聲明瞭一個指向字符變量的指針,
// 和一個字符變量;
//如下則可行:
typedef char* PCHAR;
PCHAR pa, pb;

這種用法頗有用,特別是char* pa, pb的定義,初學者每每認爲是定義了兩個字符型指針,其實不是,而用typedef char* PCHAR就不會出現這樣的問題,減小了錯誤的發生。

用途二:用在舊的C代碼中,幫助struct。

之前的代碼中,聲明struct新對象時,必需要帶上struct,即形式爲: struct 結構名對象名,如:

struct tagPOINT1{
    int x;
    int y; 
};
struct tagPOINT1 p1;

而在C++中,則能夠直接寫:結構名對象名,即:tagPOINT1 p1;

typedef struct tagPOINT{
    int x;
    int y;
}POINT;
POINT p1;

這樣就比原來的方式少寫了一個struct,比較省事,尤爲在大量使用的時候,或許,在C++中,typedef的這種用途二不是很大,可是理解了它,對掌握之前的舊代碼仍是有幫助的,畢竟咱們在項目中有可能會遇到較早些年代遺留下來的代碼。

用途三:用typedef來定義與平臺無關的類型。

好比定義一個叫 REAL 的浮點類型,在目標平臺一上,讓它表示最高精度的類型爲:

typedef long double REAL;

在不支持long double的平臺二上,改成:

typedef double REAL;

在連double都不支持的平臺三上,改成:

typedef float REAL;

也就是說,當跨平臺時,只要改下typedef自己就行,不用對其餘源碼作任何修改。
標準庫就普遍使用了這個技巧,好比size_t。另外,由於typedef是定義了一種類型的新別名,不是簡單的字符串替換,因此它比宏來得穩健。
這個優勢在咱們寫代碼的過程當中能夠減小很多代碼量哦!

用途四:爲複雜的聲明定義一個新的簡單的別名。

方法是:在原來的聲明裏逐步用別名替換一部分複雜聲明,如此循環,把帶變量名的部分留到最後替換,獲得的就是原聲明的最簡化版。舉例:
原聲明:

void (*b[10]) (void (*)());

變量名爲b,先替換右邊部分括號裏的,pFunParam爲別名一:

typedef void (*pFunParam)();

再替換左邊的變量b,pFunx爲別名二:

typedef void (*pFunx)(pFunParam);

原聲明的最簡化版:

pFunx b[10];

原聲明:

doube(*)() (*e)[9];

變量名爲e,先替換左邊部分,pFuny爲別名一:

typedef double(*pFuny)();

再替換右邊的變量e,pFunParamy爲別名二

typedef pFuny (*pFunParamy)[9];

原聲明的最簡化版:

pFunParamy e;

理解複雜聲明可用的「右左法則」:從變量名看起,先往右,再往左,碰到一個圓括號就調轉閱讀的方向;括號內分析完就跳出括號,仍是按先右後左的順序,如此循環,直到整個聲明分析完。舉例:

int (*func)(int *p);

首先找到變量名func,外面有一對圓括號,並且左邊是一個號,這說明func是一個指針;而後跳出這個圓括號,先看右邊,又遇到圓括號,這說明(func)是一個函數,因此func是一個指向這類函數的指針,即函數指針,這類函數具備int*類型的形參,返回值類型是int。

int (*func[5])(int *);

func右邊是一個[]運算符,說明func是具備5個元素的數組;func的左邊有一個,說明func的元素是指針(注意這裏的不是修飾func,而是修飾func[5]的,緣由是[]運算符優先級比高,func先跟[]結合)。跳出這個括號,看右邊,又遇到圓括號,說明func數組的元素是函數類型的指針,它指向的函數具備int類型的形參,返回值類型爲int。這種用法是比較複雜的,出現的頻率也很多,每每在看到這樣的用法卻不能理解,相信以上的解釋能有所幫助。

7.6.若是用calloc分配一個long型的數組,數組的初始值是否爲0?若是用calloc分配一個指針數組,數組的初始值是否爲空指針?

是的。

7.7.size命令的輸出結果,爲何沒有給出堆和棧的大小?

答案說的:只有經過exec函數執行一個程序時,纔會分配堆和堆棧。

7.10.咱們不能將一個指針返回給一個自動變量,下面的程序是否正確?
int f1(int val){
    int *ptr;
    if(val == 0){
        int val;
        val = 5;
        ptr = &val;
    }
    return (*ptr + 1);
}

這段代碼確定是不正確的,當if語句塊結束後,局部變量val會被釋放,指針將指向未被定義的空間,成爲所謂的「懸空指針」。這樣運行的話會提示Debug Error的。

The End

相關文章
相關標籤/搜索