C程序內(nèi)存布局

作為計(jì)算機(jī)專業(yè)的來說,程序入門基本都是從C語言開始的,了解C程序中的內(nèi)存布局,對我們了解整個(gè)程序運(yùn)行,分析程序出錯(cuò)原因,會(huì)起到事半功倍的作用 。
C程序的內(nèi)存布局包含五個(gè)段,分別是STACK(棧段),HEAP(堆段),BSS(以符號開頭的塊),DS(數(shù)據(jù)段)和TEXT(文本段)。
每個(gè)段都有自己的讀取,寫入和可執(zhí)行權(quán)限。如果程序嘗試以不允許的方式訪問內(nèi)存,則會(huì)發(fā)生段錯(cuò)誤,也就是我們常說的coredump。
段錯(cuò)誤是導(dǎo)致程序崩潰的常見問題。核心文件(核心轉(zhuǎn)儲文件)也與段錯(cuò)誤相關(guān)聯(lián),開發(fā)人員使用該文件來查找崩潰的根本原因(段錯(cuò)誤)。
下面我們將深入這五個(gè)段,更加詳細(xì)的講解每個(gè)段在程序開發(fā)或者運(yùn)行中的作用。
High?Addresses?--->?.----------------------.
????????????????????|??????Environment?????|
????????????????????|----------------------|
????????????????????|??????????????????????|???Functions?and?variable?are?declared
????????????????????|?????????STACK????????|???on?the?stack.
base?pointer?->?????|?-?-?-?-?-?-?-?-?-?-?-|
????????????????????|???????????|??????????|
????????????????????|???????????v??????????|
????????????????????:??????????????????????:
????????????????????.??????????????????????.???The?stack?grows?down?into?unused?space
????????????????????.?????????Empty????????.???while?the?heap?grows?up.?
????????????????????.??????????????????????.
????????????????????.??????????????????????.???(other?memory?maps?do?occur?here,?such?
????????????????????.??????????????????????.????as?dynamic?libraries,?and?different?memory
????????????????????:??????????????????????:????allocate)
????????????????????|???????????^??????????|
????????????????????|???????????|??????????|
?brk?point?->???????|?-?-?-?-?-?-?-?-?-?-?-|???Dynamic?memory?is?declared?on?the?heap
????????????????????|??????????HEAP????????|
????????????????????|??????????????????????|
????????????????????|----------------------|
????????????????????|??????????BSS?????????|???Uninitialized?data?(BSS)
????????????????????|----------------------|???
????????????????????|??????????Data????????|???Initialized?data?(DS)
????????????????????|----------------------|
????????????????????|??????????Text????????|???Binary?code
Low?Addresses?---->?'----------------------'
棧
它位于較高的地址,與堆段的增長和收縮方向正好相反。 函數(shù)的局部變量存在于棧上 調(diào)用函數(shù)時(shí),將在棧中創(chuàng)建一個(gè)棧幀。 每個(gè)函數(shù)都有一個(gè)棧幀。 棧幀包含函數(shù)的局部變量參數(shù)和返回值。 函數(shù)變量在調(diào)用時(shí)被壓入棧,返回時(shí)將函數(shù)變量從棧彈出。 SP(棧指針)寄存器指向棧的頂部。
#include?
int?main(void)?{
????int?data;?//?局部變量,存儲在棧上
????return?0;
}
堆
用于在運(yùn)行時(shí)分配內(nèi)存。 由內(nèi)存管理函數(shù)(如malloc、calloc、free等)管理的堆區(qū)域,這些函數(shù)可以在內(nèi)部使用brk和sbrk系統(tǒng)調(diào)用來調(diào)整其大小。 堆區(qū)域由進(jìn)程中的所有共享庫和動(dòng)態(tài)加載的模塊共享。 它在堆棧的相反方向上增長和收縮。
#include?
int?main(void)?{
????char?*pStr?=?malloc(sizeof(char)*4);?//pStr指向堆地址
????return?0;
}
BSS(未初始化的數(shù)據(jù)塊)
包含所有未初始化的全局和靜態(tài)變量。 此段中的所有變量都由零或者空指針初始化。 程序加載器在加載程序時(shí)為BSS節(jié)分配內(nèi)存。
#include?
int?data1;?//?未初始化的全局變量存儲在BSS段
int?main(void)?{
????static?int?data2;??//?未初始化的靜態(tài)變量存儲在BSS段
????return?0;
}
DS(初始化的數(shù)據(jù)塊)
包含顯式初始化的全局變量和靜態(tài)變量。 此段的大小由程序源代碼中值的大小決定,在運(yùn)行時(shí)不會(huì)更改。 它具有讀寫權(quán)限,因此可以在運(yùn)行時(shí)更改此段的變量值。 該段可進(jìn)一步分為初始化只讀區(qū)和初始化讀寫區(qū)。
#include?
int?data1?=?10?;?//初始化的全局變量存儲在DS段
int?main(void)?{
????static?int?data2?=?3;??//初始化的靜態(tài)變量存儲在DS段
????return?0;
}
TEXT
該段包含已編譯程序的二進(jìn)制文件。 該段是一個(gè)只讀段,用于防止程序被意外修改。 該段是可共享的,因此對于文本編輯器等頻繁執(zhí)行的程序,內(nèi)存中只需要一個(gè)副本。
深入
現(xiàn)在有一個(gè)簡單的程序,代碼如下:
#include??
??
int?main(void)?{?
????return?0;?
}
我們通過如下命令進(jìn)行編譯
gcc?-g?a.cc?-o?a
然后通過size命令,可以看到各個(gè)段的大小
[root@build?src]#?gcc?a.c?-o?a
[root@build?src]#?size?a
???text????data?????bss?????dec?????hex?filename
???1040?????484??????16????1540?????604?a
其中前三列分別為可執(zhí)行程序a的text、data以及bss段的大小,第四列為該三段大小之和,第四列為該大小的十六進(jìn)制表示,最后一列是文件名。
增加一個(gè)未初始化的靜態(tài)變量
#include??
????
int?main(void)?{?
????static?int?data;
????return?0;?
}
通過size命令
[root@build?src]#?size?a
???text????data?????bss?????dec?????hex?filename
???1040?????484??????24????1548?????60c?a
從上面可以看出,bss段size變大
增加一個(gè)初始化的靜態(tài)變量
#include??
????
int?main(void)?{?
????static?int?data?=?10;
????return?0;?
}
通過size命令
[root@build?src]#?size?a
???text????data?????bss?????dec?????hex?filename
???1040?????488??????16????1544?????608?a
從上面可以看出,data段size變大
增加一個(gè)未初始化的全局變量
#include??
int?data;
int?main(void)?{?
????return?0;?
}
通過size命令
[root@build?src]#?size?a
???text????data?????bss?????dec?????hex?filename
???1040?????484??????24????1548?????60c?a
從上面可以看出,bss段size變大
數(shù)據(jù)段的只讀區(qū)域和讀寫區(qū)域
#include?
char?str[]=?"Hello?world";
int?main(void)?{
????printf("%s\n",str);
????str[0]='K';
????printf("%s\n",str);
????return?0;
}
輸出
Hello?world
Kello?world
可以看到上面的示例str是一個(gè)全局?jǐn)?shù)組,因此它將進(jìn)入數(shù)據(jù)段。還可以看到能夠更改該值,因此它具有讀取和寫入權(quán)限。
現(xiàn)在查看其他示例代碼
#include?
char?*str=?"Hello?world";
int?main(void)?{
????str[0]='K';
????printf("%s\n",str);
????return?0;
}
在上面的示例中,我們無法更改數(shù)組字符是因?yàn)樗俏淖肿址3A孔址粌H會(huì)出現(xiàn)在數(shù)據(jù)部分,而且所有類型的const全局?jǐn)?shù)據(jù)都將進(jìn)入該部分。
數(shù)據(jù)塊只讀部分,通常除了const變量和常量字符串外,程序的文本部分(通常是.rodata段)也存在于數(shù)據(jù)塊的只讀部分,因?yàn)橥ǔo法通過程序進(jìn)行修改。
