通常在代碼中產(chǎn)生的bug,往往是源于概念不清晰。知己知彼百戰(zhàn)不殆,對(duì)內(nèi)存這塊了如指掌,能極大優(yōu)化代碼的性能。
一、內(nèi)存四區(qū)建立流程講解

如上圖所示,首先操作系統(tǒng)要把物理硬盤(pán)代碼加載到內(nèi)存中,加載完成后,操作系統(tǒng)會(huì)將代碼分成四個(gè)區(qū)域,即堆區(qū)、棧區(qū)、全局區(qū)和常量區(qū)。最后,操作系統(tǒng)會(huì)找到main函數(shù)入口執(zhí)行程序。這就是內(nèi)存四區(qū)執(zhí)行流程的說(shuō)明。
二、內(nèi)存四區(qū)存儲(chǔ)說(shuō)明
棧區(qū):由編譯器自動(dòng)分配釋放,存放函數(shù)的參數(shù)值、局部變量等。
堆區(qū):由程序員動(dòng)態(tài)申請(qǐng)與釋放內(nèi)存,通常用于存放new/malloc關(guān)鍵字創(chuàng)建的值。如果程序員不釋放,程序結(jié)束時(shí)會(huì)由操作系統(tǒng)回收。
全局區(qū):也叫靜態(tài)區(qū),用于存放常量和全局變量,由操作系統(tǒng)管理。全局變量和靜態(tài)變量存儲(chǔ)在一起,初始化的全局變量和靜態(tài)變量在同一塊區(qū)域,未初始化的存儲(chǔ)在相鄰的另一區(qū)域內(nèi)。
程序代碼區(qū):存放函數(shù)體的二進(jìn)制代碼,由操作系統(tǒng)管理。
三、代碼示例
char * getP1(){
char *p1 = "abcdef";
return p1;
}
char * getP2(){
char *p2 = "abcdef2";
return p2;
}
int main(int argc, const char * argv[]) {
char *p1 = getP1();
char *p2 = getP2();
printf("打印地址:%s,%s\n,",p1,p2);
printf("打印內(nèi)容:%d,%d\n",p1,p2);
return 0;
}
打印的結(jié)果:

char * getP1(){
char *p1 = "abcdef";
return p1;
}
char * getP2(){
char *p2 = "abcdef";
return p2;
}
int main(int argc, const char * argv[]) {
char *p1 = getP1();
char *p2 = getP2();
printf("打印地址:%s,%s\n,",p1,p2);
printf("打印內(nèi)容:%d,%d\n",p1,p2);
return 0;
}
打印結(jié)果:

讓人疑惑的是,為何在不同函數(shù)中聲明的不變量,打印的會(huì)是同樣的內(nèi)容和地址。

在示例中的代碼中,操作系統(tǒng)進(jìn)入到main函數(shù)的入口,將變量p1,p2放入到棧中。p1和p2分別用來(lái)接收getP1()和getP2()函數(shù)返回的首地址,在64位機(jī)中,只占用內(nèi)存的8個(gè)字節(jié),在getP1()函數(shù)中,也有一個(gè)p1變量,因此把它放入到內(nèi)存的棧中,把常量“abcdef”放入到全局區(qū)中。此時(shí),函數(shù)getP1()中的p1指針是指向“abcdef”,假設(shè)該常量的值是“0xa11”,那就將這個(gè)地址賦給函數(shù)中的p1,函數(shù)的p1要將首地址放回給main函數(shù)中的p1變量。那么main函數(shù)中的p1也會(huì)指向上文中的首地址。這就是p1指針的內(nèi)存解析說(shuō)明。
在編譯程序時(shí),若編譯器檢測(cè)到全局區(qū)有兩個(gè)同樣的值,它就會(huì)代碼進(jìn)行優(yōu)化,在內(nèi)存中只保留一份。當(dāng)這兩個(gè)相同的值放置到全局區(qū)時(shí),他會(huì)統(tǒng)一將這兩個(gè)值合成一個(gè),以避免造成內(nèi)存空間的浪費(fèi)。
所以,當(dāng)編譯器發(fā)現(xiàn)兩個(gè)函數(shù)中變量的值相同之后,就將它們合二為一,p2指針按照上文中p1的分配方式對(duì)內(nèi)存進(jìn)行分配。到最后,就會(huì)發(fā)現(xiàn)p2指針與p1指針指向了相同的地址,且它們的內(nèi)容也是相同的。這就是靜態(tài)內(nèi)存區(qū)域的全部解析。