程序是如何執行有關的c程序的?linux
C程序老是從main
函數開始執行。main
函數的原型是程序員
int main(int argc,char *argv[]);
其中,argc
是命令行參數的數目,argv
是指向參數的各個指針所構成的數組。shell
當內核執行C程序時(使用一個exec
函數),在調用main
前先調用一個特殊的啓動例程。可執行程序文件將此啓動例程指定爲程序的起始地址——這是由鏈接編輯器設置的,而鏈接編輯器則由C編譯器(一般是cc)調用。啓動例程從內核取得命令行參數和環境變量值,而後爲按上述方式調用main函數作好安排。數組
有8種方式使進程終止,其中5種爲正常終止,它們是:編輯器
main
返回。exit
。_exit
或_Exit
。pthread_exit
異常終止有3種方式,它們是:函數
abort
;從歷史上講,C程序一直由下面幾部分組成:工具
int maxcount = 99;
long sum[1000];
環境字符串的形式一般以下:佈局
name=value
環境表(指向實際name=value
字符串的指針數組)和環境字符串一般存放在進程存儲空間的頂部(棧之上)。學習
在C中,goto
語句是不能跨越函數的,而執行這類跳轉功能的是函數setjmp
和longjmp
。可是記住,對於longjmp
,大多數實現並不回滾這些自動變量和寄存器變量的值。命令行
每一個進程都有一組資源限制,其中一些能夠用getrlimit
和setrlimit
函數查詢和更改。
exit
或return
的程序,則程序的返回代碼爲13(用shell來grep檢查),解釋其緣由。緣由在於printf
的返回值(輸出的字符數)變成了main
函數的返回值。固然,並非全部的系統都會出現該狀況。
當程序處於交互運行方式時,標準輸出一般處於行緩衝方式,因此當輸出換行符時,上次的結果才被真正輸出。若是標準輸出被定向到一個文件而處於徹底緩衝方式,則當標準I/O清理操做執行時,結果才真正被輸出。
因爲 argc
和argv
的副本不像environ
同樣保存在全局變量中,因此大多數UNIX系統中沒有辦法實現。(而有關environ
環境變量的話,能夠經過函數getenv
來調用,由於命令行參數和環境變量位於棧之上的高地址)
緣由在於當程序上對一個空指針(地址爲NULL,即0)進行解引用操做時,能夠因爲訪問不到想要的地址而終止程序,防止程序訪問到意想不到的數據,在程序開發中,若是指針指向或者操做了意想不到的數據都會視爲危險的行爲。
關於空指針的解引用,在網上找到:
空指針解引用是否致使異常應該是硬件設備和OS組合決定的。之前在VXwork下工做,空指針也能夠解引用,能夠訪問內存0地址,還能夠修改內容。這種狀況下,爲了便於程序員debug,印象中咱們大概是採用了對於0地址內容監控,若是內容有改動則報告或者crash。
咱們先看一下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。這種用法是比較複雜的,出現的頻率也很多,每每在看到這樣的用法卻不能理解,相信以上的解釋能有所幫助。
是的。
答案說的:只有經過exec
函數執行一個程序時,纔會分配堆和堆棧。
int f1(int val){ int *ptr; if(val == 0){ int val; val = 5; ptr = &val; } return (*ptr + 1); }
這段代碼確定是不正確的,當if語句塊結束後,局部變量val會被釋放,指針將指向未被定義的空間,成爲所謂的「懸空指針」。這樣運行的話會提示Debug Error的。