一. C語言概述
歡迎大家來到c語言的世界,c語言是一種強(qiáng)大的專業(yè)化的編程語言寥袭。
1.1 C語言的起源
貝爾實(shí)驗(yàn)室的Dennis Ritchie在1972年開發(fā)了C,當(dāng)時(shí)他正與ken Thompson一起設(shè)計(jì)UNIX操作系統(tǒng)杰扫,然而涉波,C并不是完全由Ritchie構(gòu)想出來的苍日。它來自Thompson的B語言辜纲。
1.2 使用C語言的理由
在過去的幾十年中,c語言已成為最流行和最重要的編程語言之一扫俺。它之所以得到發(fā)展狼纬,是因?yàn)槿藗儑L試使用它后都喜歡它疗琉。過去很多年中,許多人從c語言轉(zhuǎn)而使用更強(qiáng)大的c++語言柠贤,但c有其自身的優(yōu)勢种吸,仍然是一種重要的語言镜盯,而且它還是學(xué)習(xí)c++的必經(jīng)之路速缆。
高效性艺糜。c語言是一種高效的語言。c表現(xiàn)出通常只有匯編語言才具有的精細(xì)的控制能力(匯編語言是特定cpu設(shè)計(jì)所采用的一組內(nèi)部制定的助記符真慢。不同的cpu類型使用不同的匯編語言)。如果愿意朗鸠,您可以細(xì)調(diào)程序以獲得最大的速度或最大的內(nèi)存使用率烛占。
可移植性。c語言是一種可移植的語言弦赖。意味著蹬竖,在一個(gè)系統(tǒng)上編寫的c程序經(jīng)過很少改動(dòng)或不經(jīng)過修改就可以在其他的系統(tǒng)上運(yùn)行。
強(qiáng)大的功能和靈活性。c強(qiáng)大而又靈活阴绢。比如強(qiáng)大靈活的UNIX操作系統(tǒng)便是用c編寫的呻袭。其他的語言(Perl左电、Python段誊、BASIC枕扫、Pascal)的許多編譯器和解釋器也都是用c編寫的诗鸭。結(jié)果是當(dāng)你在一臺(tái)Unix機(jī)器上使用Python時(shí)锻弓,最終由一個(gè)c程序負(fù)責(zé)生成最后的可執(zhí)行程序青灼。
1.3 C語言標(biāo)準(zhǔn)
1.3.1 K&R C
起初,C語言沒有官方標(biāo)準(zhǔn)。1978年由美國電話電報(bào)公司(AT&T)貝爾實(shí)驗(yàn)室正式發(fā)表了C語言筋粗。布萊恩?柯林漢(Brian Kernighan) 和 丹尼斯?里奇(Dennis Ritchie) 出版了一本書,名叫《The C Programming Language》沛婴。這本書被 C語言開發(fā)者們稱為K&R宫仗,很多年來被當(dāng)作 C語言的非正式的標(biāo)準(zhǔn)說明。人們稱這個(gè)版本的 C語言為K&R C毅贮。
K&R C主要介紹了以下特色:結(jié)構(gòu)體(struct)類型;長整數(shù)(long int)類型瑰煎;無符號整數(shù)(unsigned int)類型;把運(yùn)算符=+和=-改為+=和-=赋铝。因?yàn)?+和=-會(huì)使得編譯器不知道使用者要處理i = -10還是i =- 10农尖,使得處理上產(chǎn)生混淆。
即使在后來ANSI C標(biāo)準(zhǔn)被提出的許多年后窟扑,K&R C仍然是許多編譯器的最準(zhǔn)要求,許多老舊的編譯器仍然運(yùn)行K&R C的標(biāo)準(zhǔn)。
1.3.2 ANSI C/C89標(biāo)準(zhǔn)
1970到80年代洗显,C語言被廣泛應(yīng)用处窥,從大型主機(jī)到小型微機(jī)滔驾,也衍生了C語言的很多不同版本。1983年俄讹,美國國家標(biāo)準(zhǔn)協(xié)會(huì)(ANSI)成立了一個(gè)委員會(huì)X3J11哆致,來制定 C語言標(biāo)準(zhǔn)。
1989年患膛,美國國家標(biāo)準(zhǔn)協(xié)會(huì)(ANSI)通過了C語言標(biāo)準(zhǔn)摊阀,被稱為ANSI X3.159-1989 "Programming Language C"。因?yàn)檫@個(gè)標(biāo)準(zhǔn)是1989年通過的踪蹬,所以一般簡稱C89標(biāo)準(zhǔn)枝缔。有些人也簡稱ANSI C,因?yàn)檫@個(gè)標(biāo)準(zhǔn)是美國國家標(biāo)準(zhǔn)協(xié)會(huì)(ANSI)發(fā)布的酝豪。
1990年揉阎,國際標(biāo)準(zhǔn)化組織(ISO)和國際電工委員會(huì)(IEC)把C89標(biāo)準(zhǔn)定為C語言的國際標(biāo)準(zhǔn)垮衷,命名為ISO/IEC 9899:1990 - Programming languages -- C[5]? 徐许。因?yàn)榇藰?biāo)準(zhǔn)是在1990年發(fā)布的,所以有些人把簡稱作C90標(biāo)準(zhǔn)吨枉。不過大多數(shù)人依然稱之為C89標(biāo)準(zhǔn),因?yàn)榇藰?biāo)準(zhǔn)與ANSI C89標(biāo)準(zhǔn)完全等同堪滨。
1994年了讨,國際標(biāo)準(zhǔn)化組織(ISO)和國際電工委員會(huì)(IEC)發(fā)布了C89標(biāo)準(zhǔn)修訂版伶棒,名叫ISO/IEC 9899:1990/Cor 1:1994[6]? 畴蹭,有些人簡稱為C94標(biāo)準(zhǔn)提澎。
1995年,國際標(biāo)準(zhǔn)化組織(ISO)和國際電工委員會(huì)(IEC)再次發(fā)布了C89標(biāo)準(zhǔn)修訂版寺庄,名叫ISO/IEC 9899:1990/Amd 1:1995 - C Integrity[7]? 话速,有些人簡稱為C95標(biāo)準(zhǔn)。
1.3.3 C99標(biāo)準(zhǔn)
1999年1月错维,國際標(biāo)準(zhǔn)化組織(ISO)和國際電工委員會(huì)(IEC)發(fā)布了C語言的新標(biāo)準(zhǔn)僧界,名叫ISO/IEC 9899:1999 - Programming languages -- C 举反,簡稱C99標(biāo)準(zhǔn)牧挣。這是C語言的第二個(gè)官方標(biāo)準(zhǔn)。
例如:
增加了新關(guān)鍵字 restrict醒陆,inline瀑构,_Complex,_Imaginary刨摩,_Bool
支持 long long寺晌,long double _Complex,float _Complex 這樣的類型
支持了不定長的數(shù)組澡刹。數(shù)組的長度就可以用變量了呻征。聲明類型的時(shí)候呢,就用 int a[*] 這樣的寫法。不過考慮到效率和實(shí)現(xiàn)罢浇,這玩意并不是一個(gè)新類型陆赋。
二、內(nèi)存分區(qū)
2.1 數(shù)據(jù)類型
2.1.1 數(shù)據(jù)類型概念
什么是數(shù)據(jù)類型嚷闭?為什么需要數(shù)據(jù)類型?
數(shù)據(jù)類型是為了更好進(jìn)行內(nèi)存的管理,讓編譯器能確定分配多少內(nèi)存攒岛。
我們現(xiàn)實(shí)生活中,狗是狗胞锰,鳥是鳥等等灾锯,每一種事物都有自己的類型,那么程序中使用數(shù)據(jù)類型也是來源于生活胜蛉。
當(dāng)我們給狗分配內(nèi)存的時(shí)候挠进,也就相當(dāng)于給狗建造狗窩,給鳥分配內(nèi)存的時(shí)候誊册,也就是給鳥建造一個(gè)鳥窩领突,我們可以給他們各自建造一個(gè)別墅,但是會(huì)造成內(nèi)存的浪費(fèi)案怯,不能很好的利用內(nèi)存空間君旦。
我們在想,如果給鳥分配內(nèi)存嘲碱,只需要鳥窩大小的空間就夠了金砍,如果給狗分配內(nèi)存,那么也只需要狗窩大小的內(nèi)存麦锯,而不是給鳥和狗都分配一座別墅恕稠,造成內(nèi)存的浪費(fèi)。
當(dāng)我們定義一個(gè)變量扶欣,a = 10,編譯器如何分配內(nèi)存鹅巍?計(jì)算機(jī)只是一個(gè)機(jī)器千扶,它怎么知道用多少內(nèi)存可以放得下10?
所以說骆捧,數(shù)據(jù)類型非常重要澎羞,它可以告訴編譯器分配多少內(nèi)存可以放得下我們的數(shù)據(jù)。
狗窩里面是狗敛苇,鳥窩里面是鳥妆绞,如果沒有數(shù)據(jù)類型,你怎么知道冰箱里放得是一頭大象!
數(shù)據(jù)類型基本概念:
類型是對數(shù)據(jù)的抽象;
類型相同的數(shù)據(jù)具有相同的表示形式枫攀、存儲(chǔ)格式以及相關(guān)操作;
程序中所有的數(shù)據(jù)都必定屬于某種數(shù)據(jù)類型;
數(shù)據(jù)類型可以理解為創(chuàng)建變量的模具: 固定大小內(nèi)存的別名;
2.1.2 數(shù)據(jù)類型別名
typedef?unsigned?int?u32;
typedef?struct?_PERSON{
char?name[64];
int?age;
}Person;
voidtest(){
u32?val;?//相當(dāng)于?unsigned?int?val;
Person?person;?//相當(dāng)于?struct?PERSON?person;
}
2.1.3 void數(shù)據(jù)類型
void字面意思是”無類型”,void* 無類型指針括饶,無類型指針可以指向任何類型的數(shù)據(jù)。
void定義變量是沒有任何意義的脓豪,當(dāng)你定義void a巷帝,編譯器會(huì)報(bào)錯(cuò)。
void真正用在以下兩個(gè)方面:
對函數(shù)返回的限定扫夜;
對函數(shù)參數(shù)的限定楞泼;
//1.?void修飾函數(shù)參數(shù)和函數(shù)返回
void?test01(void){
printf("hello?world");
}
//2.?不能定義void類型變量
voidtest02(){
void?val;?//報(bào)錯(cuò)
}
//3.?void*?可以指向任何類型的數(shù)據(jù),被稱為萬能指針
voidtest03(){
int?a?=?10;
void*?p?=?NULL;
p?=?&a;
printf("a:%d\n",*(int*)p);
char?c?='a';
p?=?&c;
printf("c:%c\n",*(char*)p);
}
//4.?void*?常用于數(shù)據(jù)類型的封裝
voidtest04(){
//void?*?memcpy(void?*?_Dst,?const?void?*?_Src,?size_t?_Size);
}
2.1.4 sizeof 操作符
sizeof 是 c語言中的一個(gè)操作符笤闯,類似于++堕阔、--等等。sizeof 能夠告訴我們編譯器為某一特定數(shù)據(jù)或者某一個(gè)類型的數(shù)據(jù)在內(nèi)存中分配空間時(shí)分配的大小颗味,大小以字節(jié)為單位超陆。
基本語法:
sizeof(變量);
sizeof?變量;
sizeof(類型);
sizeof 注意點(diǎn):
sizeof返回的占用空間大小是為這個(gè)變量開辟的大小浦马,而不只是它用到的空間时呀。和現(xiàn)今住房的建筑面積和實(shí)用面積的概念差不多。所以對結(jié)構(gòu)體用的時(shí)候晶默,大多情況下就得考慮字節(jié)對齊的問題了谨娜;
sizeof返回的數(shù)據(jù)結(jié)果類型是unsigned int;
要注意數(shù)組名和指針變量的區(qū)別磺陡。通常情況下趴梢,我們總覺得數(shù)組名和指針變量差不多,但是在用sizeof的時(shí)候差別很大币他,對數(shù)組名用sizeof返回的是整個(gè)數(shù)組的大小坞靶,而對指針變量進(jìn)行操作的時(shí)候返回的則是指針變量本身所占得空間,在32位機(jī)的條件下一般都是4蝴悉。而且當(dāng)數(shù)組名作為函數(shù)參數(shù)時(shí)彰阴,在函數(shù)內(nèi)部,形參也就是個(gè)指針拍冠,所以不再返回?cái)?shù)組的大心蛘狻廉丽;
//1.?sizeof基本用法
voidtest01(){
int?a?=?10;
printf("len:%d\n",?sizeof(a));
printf("len:%d\n",?sizeof(int));
printf("len:%d\n",?sizeof?a);
}
//2.?sizeof?結(jié)果類型
voidtest02(){
unsigned?int?a?=?10;
if(a?-?11?<?0){
printf("結(jié)果小于0\n");
}
else{
printf("結(jié)果大于0\n");
}
int?b?=?5;
if(sizeof(b)?-?10?<?0){
printf("結(jié)果小于0\n");
}
else{
printf("結(jié)果大于0\n");
}
}
//3.?sizeof?碰到數(shù)組
void?TestArray(int?arr[]){
printf("TestArray?arr?size:%d\n",sizeof(arr));
}
voidtest03(){
int?arr[]?=?{?10,?20,?30,?40,?50?};
printf("array?size:?%d\n",sizeof(arr));
//數(shù)組名在某些情況下等價(jià)于指針
int*?pArr?=?arr;
printf("arr[2]:%d\n",pArr[2]);
printf("array?size:?%d\n",?sizeof(pArr));
//數(shù)組做函數(shù)函數(shù)參數(shù),將退化為指針,在函數(shù)內(nèi)部不再返回?cái)?shù)組大小
TestArray(arr);
}
2.1.5 數(shù)據(jù)類型總結(jié)
數(shù)據(jù)類型本質(zhì)是固定內(nèi)存大小的別名妻味,是個(gè)模具,C語言規(guī)定:通過數(shù)據(jù)類型定義變量欣福;
數(shù)據(jù)類型大小計(jì)算(sizeof)责球;
可以給已存在的數(shù)據(jù)類型起別名typedef;
數(shù)據(jù)類型的封裝(void 萬能類型)拓劝;
2.2 變量
2.1.1 變量的概念
既能讀又能寫的內(nèi)存對象雏逾,稱為變量;
若一旦初始化后不能修改的對象則稱為常量郑临。
變量定義形式:?類型??標(biāo)識(shí)符,?標(biāo)識(shí)符,?…?,?標(biāo)識(shí)符
2.1.2 變量名的本質(zhì)
變量名的本質(zhì):一段連續(xù)內(nèi)存空間的別名栖博;
程序通過變量來申請和命名內(nèi)存空間 int a = 0;
通過變量名訪問內(nèi)存空間厢洞;
不是向變量名讀寫數(shù)據(jù)仇让,而是向變量所代表的內(nèi)存空間中讀寫數(shù)據(jù);
修改變量的兩種方式:
voidtest(){
int?a?=?10;
//1.?直接修改
a?=?20;
printf("直接修改,a:%d\n",a);
//2.?間接修改
int*?p?=?&a;
*p?=?30;
printf("間接修改,a:%d\n",?a);
}
2.3 程序的內(nèi)存分區(qū)模型
2.3.1 內(nèi)存分區(qū)
2.3.1.1 運(yùn)行之前
我們要想執(zhí)行我們編寫的c程序躺翻,那么第一步需要對這個(gè)程序進(jìn)行編譯丧叽。
1)預(yù)處理:宏定義展開、頭文件展開公你、條件編譯踊淳,這里并不會(huì)檢查語法
2)編譯:檢查語法,將預(yù)處理后文件編譯生成匯編文件
3)匯編:將匯編文件生成目標(biāo)文件(二進(jìn)制文件)
4)鏈接:將目標(biāo)文件鏈接為可執(zhí)行程序
? 代碼區(qū)
存放 CPU 執(zhí)行的機(jī)器指令陕靠。通常代碼區(qū)是可共享的(即另外的執(zhí)行程序可以調(diào)用它)迂尝,使其可共享的目的是對于頻繁被執(zhí)行的程序,只需要在內(nèi)存中有一份代碼即可剪芥。代碼區(qū)通常是只讀的垄开,使其只讀的原因是防止程序意外地修改了它的指t令。另外粗俱,代碼區(qū)還規(guī)劃了局部變量的相關(guān)信息说榆。
? 全局初始化數(shù)據(jù)區(qū)/靜態(tài)數(shù)據(jù)區(qū)(data段)
該區(qū)包含了在程序中明確被初始化的全局變量、已經(jīng)初始化的靜態(tài)變量(包括全局靜態(tài)變量和t)和常量數(shù)據(jù)(如字符串常量)寸认。
? 未初始化數(shù)據(jù)區(qū)(又叫 bss 區(qū))
存入的是全局未初始化變量和未初始化靜態(tài)變量签财。未初始化數(shù)據(jù)區(qū)的數(shù)據(jù)在程序開始執(zhí)行之前被內(nèi)核初始化為 0 或者空(NULL)。
總體來講說偏塞,程序源代碼被編譯之后主要分成兩種段:程序指令(代碼區(qū))和程序數(shù)據(jù)(數(shù)據(jù)區(qū))唱蒸。代碼段屬于程序指令,而數(shù)據(jù)域段和.bss段屬于程序數(shù)據(jù)灸叼。
那為什么把程序的指令和程序數(shù)據(jù)分開呢神汹?
程序被load到內(nèi)存中之后庆捺,可以將數(shù)據(jù)和代碼分別映射到兩個(gè)內(nèi)存區(qū)域。由于數(shù)據(jù)區(qū)域?qū)M(jìn)程來說是可讀可寫的屁魏,而指令區(qū)域?qū)Τ绦騺碇v說是只讀的滔以,所以分區(qū)之后呢,可以將程序指令區(qū)域和數(shù)據(jù)區(qū)域分別設(shè)置成可讀可寫或只讀氓拼。這樣可以防止程序的指令有意或者無意被修改你画;
當(dāng)系統(tǒng)中運(yùn)行著多個(gè)同樣的程序的時(shí)候,這些程序執(zhí)行的指令都是一樣的桃漾,所以只需要內(nèi)存中保存一份程序的指令就可以了坏匪,只是每一個(gè)程序運(yùn)行中數(shù)據(jù)不一樣而已,這樣可以節(jié)省大量的內(nèi)存撬统。比如說之前的Windows Internet Explorer 7.0運(yùn)行起來之后适滓, 它需要占用112 844KB的內(nèi)存,它的私有部分?jǐn)?shù)據(jù)有大概15 944KB恋追,也就是說有96 900KB空間是共享的凭迹,如果程序中運(yùn)行了幾百個(gè)這樣的進(jìn)程,可以想象共享的方法可以節(jié)省大量的內(nèi)存苦囱。
2.3.1.1 運(yùn)行之后
程序在加載到內(nèi)存前蕊苗,代碼區(qū)和全局區(qū)(data和bss)的大小就是固定的,程序運(yùn)行期間不能改變沿彭。然后朽砰,運(yùn)行可執(zhí)行程序,操作系統(tǒng)把物理硬盤程序load(加載)到內(nèi)存喉刘,除了根據(jù)可執(zhí)行程序的信息分出代碼區(qū)(text)瞧柔、數(shù)據(jù)區(qū)(data)和未初始化數(shù)據(jù)區(qū)(bss)之外,還額外增加了棧區(qū)睦裳、堆區(qū)造锅。
? 代碼區(qū)(text segment)
加載的是可執(zhí)行文件代碼段,所有的可執(zhí)行代碼都加載到代碼區(qū)廉邑,這塊內(nèi)存是不可以在運(yùn)行期間修改的哥蔚。
? 未初始化數(shù)據(jù)區(qū)(BSS)
加載的是可執(zhí)行文件BSS段,位置可以分開亦可以緊靠數(shù)據(jù)段蛛蒙,存儲(chǔ)于數(shù)據(jù)段的數(shù)據(jù)(全局未初始化糙箍,靜態(tài)未初始化數(shù)據(jù))的生存周期為整個(gè)程序運(yùn)行過程。
? 全局初始化數(shù)據(jù)區(qū)/靜態(tài)數(shù)據(jù)區(qū)(data segment)
加載的是可執(zhí)行文件數(shù)據(jù)段牵祟,存儲(chǔ)于數(shù)據(jù)段(全局初始化深夯,靜態(tài)初始化數(shù)據(jù),文字常量(只讀))的數(shù)據(jù)的生存周期為整個(gè)程序運(yùn)行過程。
? 棧區(qū)(stack)
棧是一種先進(jìn)后出的內(nèi)存結(jié)構(gòu)咕晋,由編譯器自動(dòng)分配釋放雹拄,存放函數(shù)的參數(shù)值、返回值掌呜、局部變量等滓玖。在程序運(yùn)行過程中實(shí)時(shí)加載和釋放,因此质蕉,局部變量的生存周期為申請到釋放該段椖刈玻空間。
? 堆區(qū)(heap)
堆是一個(gè)大容器饰剥,它的容量要遠(yuǎn)遠(yuǎn)大于棧,但沒有棧那樣先進(jìn)后出的順序摧阅。用于動(dòng)態(tài)內(nèi)存分配汰蓉。堆在內(nèi)存中位于BSS區(qū)和棧區(qū)之間。一般由程序員分配和釋放棒卷,若程序員不釋放顾孽,程序結(jié)束時(shí)由操作系統(tǒng)回收。
2.3.2 分區(qū)模型
2.3.2.1 棧區(qū)
由系統(tǒng)進(jìn)行內(nèi)存的管理比规。主要存放函數(shù)的參數(shù)以及局部變量若厚。在函數(shù)完成執(zhí)行,系統(tǒng)自行釋放棧區(qū)內(nèi)存蜒什,不需要用戶管理测秸。
#char*?func(){
char?p[]?="hello?world!";?//在棧區(qū)存儲(chǔ)?亂碼
printf("%s\n",?p);
returnp;
}
voidtest(){
char*?p?=?NULL;
p?=?func();
printf("%s\n",p);
}
2.3.2.2 堆區(qū)
由編程人員手動(dòng)申請,手動(dòng)釋放灾常,若不手動(dòng)釋放霎冯,程序結(jié)束后由系統(tǒng)回收,生命周期是整個(gè)程序運(yùn)行期間钞瀑。使用malloc或者new進(jìn)行堆的申請沈撞。
char*func(){
char*?str?=?malloc(100);
strcpy(str,"hello?world!");
printf("%s\n",str);
returnstr;
}
voidtest01(){
char*?p?=?NULL;
p?=?func();
printf("%s\n",p);
}
void?allocateSpace(char*?p){
p?=?malloc(100);
strcpy(p,"hello?world!");
printf("%s\n",?p);
}
voidtest02(){
char*?p?=?NULL;
allocateSpace(p);
printf("%s\n",?p);
}
堆分配內(nèi)存API:
#include?<stdlib.h>
void?*calloc(size_t?nmemb,?size_t?size);
功能:
在內(nèi)存動(dòng)態(tài)存儲(chǔ)區(qū)中分配nmemb塊長度為size字節(jié)的連續(xù)區(qū)域。calloc自動(dòng)將分配的內(nèi)存 置0雕什。
參數(shù):
nmemb:所需內(nèi)存單元數(shù)量
size:每個(gè)內(nèi)存單元的大胁场(單位:字節(jié))
返回值:
成功:分配空間的起始地址
失敗:NULL
#include?<stdlib.h>
void?*realloc(void?*ptr,?size_t?size);
功能:
重新分配用malloc或者calloc函數(shù)在堆中分配內(nèi)存空間的大小贷岸。
realloc不會(huì)自動(dòng)清理增加的內(nèi)存壹士,需要手動(dòng)清理,如果指定的地址后面有連續(xù)的空間偿警,那么就會(huì)在已有地址基礎(chǔ)上增加內(nèi)存墓卦,如果指定的地址后面沒有空間,那么realloc會(huì)重新分配新的連續(xù)內(nèi)存户敬,把舊內(nèi)存的值拷貝到新內(nèi)存落剪,同時(shí)釋放舊內(nèi)存睁本。
參數(shù):
ptr:為之前用malloc或者calloc分配的內(nèi)存地址,如果此參數(shù)等于NULL忠怖,那么和realloc與malloc功能一致
size:為重新分配內(nèi)存的大小, 單位:字節(jié)
返回值:
成功:新分配的堆內(nèi)存地址
失斈匮摺:NULL
voidtest01(){
int*?p1?=?calloc(10,sizeof(int));
if(p1?==?NULL){
return;
}
for(int?i?=?0;?i?<?10;?i?++){
p1[i]?=?i?+?1;
}
for(int?i?=?0;?i?<?10;?i++){
printf("%d?",p1[i]);
}
printf("\n");
free(p1);
}
voidtest02(){
int*?p1?=?calloc(10,?sizeof(int));
if(p1?==?NULL){
return;
}
for(int?i?=?0;?i?<?10;?i++){
p1[i]?=?i?+?1;
}
int*?p2?=?realloc(p1,?15?*?sizeof(int));
if(p2?==?NULL){
return;
}
printf("%d\n",?p1);
printf("%d\n",?p2);
//打印
for(int?i?=?0;?i?<?15;?i++){
printf("%d?",?p2[i]);
}
printf("\n");
//重新賦值
for(int?i?=?0;?i?<?15;?i++){
p2[i]?=?i?+?1;
}
//再次打印
for(int?i?=?0;?i?<?15;?i++){
printf("%d?",?p2[i]);
}
printf("\n");
free(p2);
}
2.3.2.3 全局/靜態(tài)區(qū)
全局靜態(tài)區(qū)內(nèi)的變量在編譯階段已經(jīng)分配好內(nèi)存空間并初始化。這塊內(nèi)存在程序運(yùn)行期間一直存在,它主要存儲(chǔ)全局變量凡泣、靜態(tài)變量和常量枉疼。
注意:
(1)這里不區(qū)分初始化和未初始化的數(shù)據(jù)區(qū),是因?yàn)殪o態(tài)存儲(chǔ)區(qū)內(nèi)的變量若不顯示初始化鞋拟,則編譯器會(huì)自動(dòng)以默認(rèn)的方式進(jìn)行初始化骂维,即靜態(tài)存儲(chǔ)區(qū)內(nèi)不存在未初始化的變量。
(2)全局靜態(tài)存儲(chǔ)區(qū)內(nèi)的常量分為常變量和字符串常量贺纲,一經(jīng)初始化航闺,不可修改。靜態(tài)存儲(chǔ)內(nèi)的常變量是全局變量猴誊,與局部常變量不同潦刃,區(qū)別在于局部常變量存放于棧,實(shí)際可間接通過指針或者引用進(jìn)行修改懈叹,而全局常變量存放于靜態(tài)常量區(qū)則不可以間接修改乖杠。
(3)字符串常量存儲(chǔ)在全局/靜態(tài)存儲(chǔ)區(qū)的常量區(qū)。
int?v1?=?10;//全局/靜態(tài)區(qū)
const?int?v2?=?20;?//常量澄成,一旦初始化胧洒,不可修改
static?int?v3?=?20;?//全局/靜態(tài)區(qū)
char?*p1;?//全局/靜態(tài)區(qū),編譯器默認(rèn)初始化為NULL
//那么全局static?int?和?全局int變量有什么區(qū)別墨状?
voidtest(){
static?int?v4?=?20;?//全局/靜態(tài)區(qū)
}
char*func(){
static?char?arr[]?="hello?world!";?//在靜態(tài)區(qū)存儲(chǔ)?可讀可寫
arr[2]?='c';
char*?p?="hello?world!";?//全局/靜態(tài)區(qū)-字符串常量區(qū)
//p[2]?='c';?//只讀略荡,不可修改
printf("%d\n",arr);
printf("%d\n",p);
printf("%s\n",?arr);
returnarr;
}
voidtest(){
char*?p?=?func();
printf("%s\n",p);
}
2.3.2.4 總結(jié)
在理解C/C++內(nèi)存分區(qū)時(shí),常會(huì)碰到如下術(shù)語:數(shù)據(jù)區(qū)歉胶,堆汛兜,棧,靜態(tài)區(qū)通今,常量區(qū)粥谬,全局區(qū),字符串常量區(qū)辫塌,文字常量區(qū)漏策,代碼區(qū)等等,初學(xué)者被搞得云里霧里臼氨。在這里掺喻,嘗試捋清楚以上分區(qū)的關(guān)系。
數(shù)據(jù)區(qū)包括:堆,棧感耙,全局/靜態(tài)存儲(chǔ)區(qū)褂乍。
全局/靜態(tài)存儲(chǔ)區(qū)包括:常量區(qū),全局區(qū)即硼、靜態(tài)區(qū)逃片。
常量區(qū)包括:字符串常量區(qū)、常變量區(qū)只酥。
代碼區(qū):存放程序編譯后的二進(jìn)制代碼褥实,不可尋址區(qū)。
可以說裂允,C/C++內(nèi)存分區(qū)其實(shí)只有兩個(gè)损离,即代碼區(qū)和數(shù)據(jù)區(qū)。
2.3.3 函數(shù)調(diào)用模型
2.3.3.1 函數(shù)調(diào)用流程
棧(stack)是現(xiàn)代計(jì)算機(jī)程序里最為重要的概念之一绝编,幾乎每一個(gè)程序都使用了棧僻澎,沒有棧就沒有函數(shù),沒有局部變量瓮增,也就沒有我們?nèi)缃衲芤姷降乃杏?jì)算機(jī)的語言。在解釋為什么棧如此重要之前哩俭,我們先了解一下傳統(tǒng)的棧的定義:
在經(jīng)典的計(jì)算機(jī)科學(xué)中绷跑,棧被定義為一個(gè)特殊的容器,用戶可以將數(shù)據(jù)壓入棧中(入棧凡资,push),也可以將壓入棧中的數(shù)據(jù)彈出(出棧隙赁,pop),但是棧容器必須遵循一條規(guī)則:先入棧的數(shù)據(jù)最后出棧(First In Last Out,FILO).
在經(jīng)典的操作系統(tǒng)中,椀嗫ィ總是向下增長的厚掷。壓棧的操作使得棧頂?shù)牡刂窚p小,彈出操作使得棧頂?shù)刂吩龃蟆?/p>
棧在程序運(yùn)行中具有極其重要的地位冒黑。最重要的,棧保存一個(gè)函數(shù)調(diào)用所需要維護(hù)的信息掩驱,這通常被稱為堆棧幀(Stack Frame)或者活動(dòng)記錄(Activate Record).一個(gè)函數(shù)調(diào)用過程所需要的信息一般包括以下幾個(gè)方面:
函數(shù)的返回地址;
函數(shù)的參數(shù)欧穴;
臨時(shí)變量民逼;
保存的上下文:包括在函數(shù)調(diào)用前后需要保持不變的寄存器。
我們從下面的代碼苔可,分析以下函數(shù)的調(diào)用過程:
int?func(int?a,int?b){
int?t_a?=?a;
int?t_b?=?b;
returnt_a?+?t_b;
}
intmain(){
int?ret?=?0;
ret?=?func(10,?20);
returnEXIT_SUCCESS;
}
2.3.3.2 調(diào)用慣例
現(xiàn)在缴挖,我們大致了解了函數(shù)調(diào)用的過程,這期間有一個(gè)現(xiàn)象焚辅,那就是函數(shù)的調(diào)用者和被調(diào)用者對函數(shù)調(diào)用有著一致的理解映屋,例如,它們雙方都一致的認(rèn)為函數(shù)的參數(shù)是按照某個(gè)固定的方式壓入棧中同蜻。如果不這樣的話棚点,函數(shù)將無法正確運(yùn)行。
如果函數(shù)調(diào)用方在傳遞參數(shù)的時(shí)候先壓入a參數(shù)湾蔓,再壓入b參數(shù)瘫析,而被調(diào)用函數(shù)則認(rèn)為先壓入的是b,后壓入的是a,那么被調(diào)用函數(shù)在使用a,b值時(shí)候,就會(huì)顛倒默责。
因此贬循,函數(shù)的調(diào)用方和被調(diào)用方對于函數(shù)是如何調(diào)用的必須有一個(gè)明確的約定,只有雙方都遵循同樣的約定桃序,函數(shù)才能夠被正確的調(diào)用杖虾,這樣的約定被稱為”調(diào)用慣例(Calling Convention)”.一個(gè)調(diào)用慣例一般包含以下幾個(gè)方面:
函數(shù)參數(shù)的傳遞順序和方式
函數(shù)的傳遞有很多種方式,最常見的是通過棧傳遞媒熊。函數(shù)的調(diào)用方將參數(shù)壓入棧中奇适,函數(shù)自己再從棧中將參數(shù)取出。對于有多個(gè)參數(shù)的函數(shù)芦鳍,調(diào)用慣例要規(guī)定函數(shù)調(diào)用方將參數(shù)壓棧的順序:從左向右嚷往,還是從右向左。有些調(diào)用慣例還允許使用寄存器傳遞參數(shù)柠衅,以提高性能皮仁。
棧的維護(hù)方式
在函數(shù)將參數(shù)壓入棧中之后,函數(shù)體會(huì)被調(diào)用菲宴,此后需要將被壓入棧中的參數(shù)全部彈出,以使得棧在函數(shù)調(diào)用前后保持一致付燥。這個(gè)彈出的工作可以由函數(shù)的調(diào)用方來完成键科,也可以由函數(shù)本身來完成勋颖。
為了在鏈接的時(shí)候?qū)φ{(diào)用慣例進(jìn)行區(qū)分饭玲,調(diào)用慣例要對函數(shù)本身的名字進(jìn)行修飾。不同的調(diào)用慣例有不同的名字修飾策略矮冬。
事實(shí)上,在c語言里窑滞,存在著多個(gè)調(diào)用慣例哀卫,而默認(rèn)的是cdecl.任何一個(gè)沒有顯示指定調(diào)用慣例的函數(shù)都是默認(rèn)是cdecl慣例此改。比如我們上面對于func函數(shù)的聲明带斑,它的完整寫法應(yīng)該是:
int?_cdecl?func(int?a,int?b);
注意:cdecl不是標(biāo)準(zhǔn)的關(guān)鍵字勋磕,在不同的編譯器里可能有不同的寫法挂滓,例如gcc里就不存在_cdecl這樣的關(guān)鍵字赶站,而是使用__attribute_((cdecl)).
2.3.3.2 函數(shù)變量傳遞分析
2.3.4 棧的生長方向和內(nèi)存存放方向
//1.?棧的生長方向
voidtest01(){
int?a?=?10;
int?b?=?20;
int?c?=?30;
int?d?=?40;
printf("a?=?%d\n",?&a);
printf("b?=?%d\n",?&b);
printf("c?=?%d\n",?&c);
printf("d?=?%d\n",?&d);
//a的地址大于b的地址烙博,故而生長方向向下
}
//2.?內(nèi)存生長方向(小端模式)
voidtest02(){
//高位字節(jié)?->?地位字節(jié)
int?num?=?0xaabbccdd;
unsigned?char*?p?=?#
//從首地址開始的第一個(gè)字節(jié)
printf("%x\n",*p);
printf("%x\n",?*(p?+?1));
printf("%x\n",?*(p?+?2));
printf("%x\n",?*(p?+?3));
}