以前在學Java的時候對於Java虛擬機中的內存分佈有必定的瞭解,可是最近在看一些C,發現竟然本身對於C語言的內存分配了解的太少。linux
問題不能拖,我這就來學習一下吧,爭取一次搞定。 在任何程序設計環境及語言中,內存管理都十分重要。程序員
分析C語言內存的分佈先從Linux下可執行的C程序入手。如今有一個簡單的C源程序hello.c算法
1 #include <stdio.h> 2 #include <stdlib.h> 3 int var1 = 1; 4 5 int main(void) { 6 int var2 = 2; 7 printf("hello, world!\n"); 8 exit(0); 9 }
通過gcc hello.c進行編譯以後獲得了名爲a.out的可執行文件編程
[tuhooo@localhost leet_code]$ ls -al a.out
-rwxrwxr-x. 1 tuhooo tuhooo 8592 Jul 22 20:40 a.out數組
ls命令是查看文件的元數據信息數據結構
[tuhooo@localhost leet_code]$ file a.out
a.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=23c58f2cad39d8b15b91f0cc8129055833372afe, not stripped架構
file命令用來識別文件類型,也可用來辨別一些文件的編碼格式。app
它是經過查看文件的頭部信息來獲取文件類型,而不是像Windows經過擴展名來肯定文件類型的。框架
[tuhooo@localhost leet_code]$ size a.out函數
text | data | bss | dec | hex | filename |
(代碼區靜態數據) | (全局初始化靜態數據) | (未初始化數據區) | (十進制總和) | (十六制總和) | (文件名) |
1301 | 560 | 8 | 1869 | 74d | a.out |
顯示一個目標文件或者連接庫文件中的目標文件的各個段的大小,當沒有輸入文件名時,默認爲a.out。
size:支持的目標: elf32-i386 a.out-i386-linux efi-app-ia32 elf32-little elf32-big srec symbolsrec tekhex binary ihex trad-core。
那啥,可執行文件在存儲(也就是尚未載入到內存中)的時候,分爲:代碼區、數據區和未初始化數據區3個部分。
(1)代碼區(text segment)。存放CPU執行的機器指令(machine instructions)。一般,代碼區是可共享的(即另外的執行程序能夠調用它),由於對於頻繁被執行的程序,只須要在內存中有一份代碼便可。代碼區一般是隻讀的,使其只讀的緣由是防止程序意外地修改了它的指令。另外,代碼區還規劃了局部變量的相關信息。
(2)全局初始化數據區/靜態數據區(initialized data segment/data segment)。該區包含了在程序中明確被初始化的全局變量、靜態變量(包括全局靜態變量和局部靜態變量)和常量數據(如字符串常量)。例如,一個不在任何函數內的聲明(全局數據):
1 int maxcount = 99;
使得變量maxcount根據其初始值被存儲到初始化數據區中。
1 static mincount = 100;
這聲明瞭一個靜態數據,若是是在任何函數體外聲明,則表示其爲一個全局靜態變量,若是在函數體內(局部),則表示其爲一個局部靜態變量。另外,若是在函數名前加上static,則表示此函數只能在當前文件中被調用。
(3)未初始化數據區。亦稱BSS區(uninitialized data segment),存入的是全局未初始化變量。BSS這個叫法是根據一個早期的彙編運算符而來,這個彙編運算符標誌着一個塊的開始。BSS區的數據在程序開始執行以前被內核初始化爲0或者空指針(NULL)。例如一個不在任何函數內的聲明:
1 long sum[1000];
將變量sum存儲到未初始化數據區。
下圖所示爲可執行代碼存儲時結構和運行時結構的對照圖。一個正在運行着的C編譯程序佔用的內存分爲代碼區、初始化數據區、未初始化數據區、堆區和棧區5個部分。
再來看一張圖,多個一個命令行參數區:
(1)代碼區(text segment)。代碼區指令根據程序設計流程依次執行,對於順序指令,則只會執行一次(每一個進程),若是反覆,則須要使用跳轉指令,若是進行遞歸,則須要藉助棧來實現。代碼段: 代碼段(code segment/text segment )一般是指用來存放程序執行代碼的一塊內存區域。這部分區域的大小在程序運行前就已經肯定,而且內存區域一般屬於只讀, 某些架構也容許代碼段爲可寫,即容許修改程序。在代碼段中,也有可能包含一些只讀的常數變量,例如字符串常量等。代碼區的指令中包括操做碼和要操做的對象(或對象地址引用)。若是是當即數(即具體的數值,如5),將直接包含在代碼中;若是是局部數據,將在棧區分配空間,而後引用該數據地址;若是是BSS區和數據區,在代碼中一樣將引用該數據地址。另外,代碼段還規劃了局部數據所申請的內存空間信息。
(2)全局初始化數據區/靜態數據區(Data Segment)。只初始化一次。數據段: 數據段(data segment )一般是指用來存放程序中已初始化的全局變量的一塊內存區域。數據段屬於靜態內存分配。data段中的靜態數據區存放的是程序中已初始化的全局變量、靜態變量和常量。
(3)未初始化數據區(BSS)。在運行時改變其值。BSS 段: BSS 段(bss segment )一般是指用來存放程序中未初始化的全局變量的一塊內存區域。BSS 是英文Block Started by Symbol 的簡稱。BSS 段屬於靜態內存分配,即程序一開始就將其清零了。通常在初始化時BSS段部分將會清零。
(4)棧區(stack)。由編譯器自動分配釋放,存放函數的參數值、局部變量的值等。存放函數的參數值、局部變量的值,以及在進行任務切換時存放當前任務的上下文內容。其操做方式相似於數據結構中的棧。每當一個函數被調用,該函數返回地址和一些關於調用的信息,好比某些寄存器的內容,被存儲到棧區。而後這個被調用的函數再爲它的自動變量和臨時變量在棧區上分配空間,這就是C實現函數遞歸調用的方法。每執行一次遞歸函數調用,一個新的棧框架就會被使用,這樣這個新實例棧裏的變量就不會和該函數的另外一個實例棧裏面的變量混淆。棧(stack) :棧又稱堆棧, 是用戶存放程序臨時建立的局部變量,也就是說咱們函數括弧"{}"中定義的變量(但不包括static 聲明的變量,static 意味着在數據段中存放變量)。除此之外,在函數被調用時,其參數也會被壓入發起調用的進程棧中,而且待到調用結束後,函數的返回值也會被存放回棧中。因爲棧的先進先出特色,因此棧特別方便用來保存/ 恢復調用現場。從這個意義上講,咱們能夠把堆棧當作一個寄存、交換臨時數據的內存區。
(5)堆區(heap)。用於動態內存分配。堆在內存中位於bss區和棧區之間。通常由程序員分配和釋放,若程序員不釋放,程序結束時有可能由OS回收。堆(heap): 堆是用於存放進程運行中被動態分配的內存段,它的大小並不固定,可動態擴張或縮減。當進程調用malloc 等函數分配內存時,新分配的內存就被動態添加到堆上(堆被擴張);當利用free 等函數釋放內存時,被釋放的內存從堆中被剔除(堆被縮減)。在將應用程序加載到內存空間執行時,操做系統負責代碼段、數據段和BSS段的加載,並將在內存中爲這些段分配空間。棧段亦由操做系統分配和管理,而不須要程序員顯示地管理;堆段由程序員本身管理,即顯式地申請和釋放空間。
另外,可執行程序在運行時具備相應的程序屬性。在有操做系統支持時,這些屬性頁由操做系統管理和維護。
C語言程序編譯完成以後,已初始化的全局變量保存在DATA段中,未初始化的全局變量保存在BSS段中。TEXT和DATA段都在可執行文件中,由系統從可執行文件中加載;而BSS段不在可執行文件中,由系統初始化。BSS段只保存沒有值的變量,因此事實上它並不須要保存這些變量的映像。運行時所須要的BSS段大小記錄在目標文件中,可是BSS段並不佔據目標文件的任何空間。
以上兩圖來自於《C語言專家編程》。
在操做系統中,一個進程就是處於執行期的程序(固然包括系統資源),實際上正在執行的程序代碼的活標本。那麼進程的邏輯地址空間是如何劃分的呢?
左邊的是UNIX/LINUX系統的執行文件,右邊是對應進程邏輯地址空間的劃分狀況。
首先是堆棧區(stack),堆棧是由編譯器自動分配釋放,存放函數的參數值,局部變量的值等。其操做方式相似於數據結構中的棧。棧的申請是由系統自動分配,如在函數內部申請一個局部變量 int h,同時判別所申請空間是否小於棧的剩餘空間,如若小於的話,在堆棧中爲其開闢空間,爲程序提供內存,不然將報異常提示棧溢出。
其次是堆(heap),堆通常由程序員分配釋放, 若程序員不釋放,程序結束時可能由OS回收 。注意它與數據結構中的堆是兩回事,分配方式卻是相似於鏈表。堆的申請是由程序員本身來操做的,在C中使用malloc函數,而C++中使用new運算符,可是堆的申請過程比較複雜:當系統收到程序的申請時,會遍歷記錄空閒內存地址的鏈表,以求尋找第一個空間大於所申請空間的堆結點,而後將該結點從空閒 結點鏈表中刪除,並將該結點的空間分配給程序,此處應該注意的是有些狀況下,新申請的內存塊的首地址記錄本次分配的內存塊大小,這樣在delete尤爲是 delete[]時就能正確的釋放內存空間。
接着是全局數據區(靜態區) (static),全局變量和靜態變量的存儲是放在一塊的,初始化的全局變量和靜態變量在一塊區域, 未初始化的全局變量和未初始化的靜態變量在相鄰的另外一塊區域。 另外文字常量區,常量字符串就是放在這裏,程序結束後有系統釋放。
最後是程序代碼區,放着函數體的二進制代碼。
(1)一個進程在運行過程當中,代碼是根據流程依次執行的,只須要訪問一次,固然跳轉和遞歸有可能使代碼執行屢次,而數據通常都須要訪問屢次,所以單獨開闢空間以方便訪問和節約空間。
(2)臨時數據及須要再次使用的代碼在運行時放入棧區中,生命週期短。
(3)全局數據和靜態數據有可能在整個程序執行過程當中都須要訪問,所以單獨存儲管理。
(4)堆區由用戶自由分配,以便管理。
1 /* memory_allocate.c用於演示內存分佈狀況 */ 2 3 int a = 0; /* a在全局已初始化數據區 */ 4 char *p1; /* p1在BSS區(未初始化全局變量) */ 5 6 int main(void) { 7 int b; /* b在棧區 */ 8 char s[] = "abc"; /* s爲數組變量, 存儲在棧區 */ 9 /* "abc"爲字符串常量, 存儲在已初始化數據區 */ 10 char *p1, p2; /* p一、p2在棧區 */ 11 char *p3 = "123456"; /* "123456\0"已初始化在數據區, p3在棧區 */ 12 static int c = 0; /* c爲全局(靜態)數據, 存在於已初始化數據區 */ 13 /* 另外, 靜態數據會自動初始化 */ 14 p1 = (char *)malloc(10); /* 分配的10個字節的區域存在於堆區 */ 15 p2 = (char *)malloc(20); /* 分配得來的20個字節的區域存在於堆區 */ 16 17 free(p1); 18 free(p2); 19 }
在C語言中,對象可使用靜態或動態的方式分配內存空間。
靜態分配:編譯器在處理程序源代碼時分配。
動態分配:程序在執行時調用malloc庫函數申請分配。
靜態內存分配是在程序執行以前進行的於是效率比較高,而動態內存分配則能夠靈活的處理未知數目的。
靜態與動態內存分配的主要區別以下:
靜態對象是有名字的變量,能夠直接對其進行操做;動態對象是沒有名字的一段地址,須要經過指針間接地對它進行操做。
靜態對象的分配與釋放由編譯器自動處理;動態對象的分配與釋放必須由程序員顯式地管理,它經過malloc()和free兩個函數來完成。
如下是採用靜態分配方式的例子。
1 int a = 100;
此行代碼指示編譯器分配足夠的存儲區以存放一個整型值,該存儲區與名字a相關聯,並用數值100初始化該存儲區。
如下是採用動態分配方式的例子:
1 p1 = (char *)malloc(10*sizeof(int));
此行代碼分配了10個int類型的對象,而後返回對象在內存中的地址,接着這個地址被用來初始化指針對象p1,對於動態分配的內存惟一的訪問方式是經過指針間接地訪問,其釋放方法爲:
1 free(p1);
前面已經介紹過,棧是由編譯器在須要時分配的,不須要時自動清除的變量存儲區。裏面的變量一般是局部變量、函數參數等。堆是由malloc()函數分配的內存塊,內存釋放由程序員手動控制,在C語言爲free函數完成。棧和堆的主要區別有如下幾點:
(1)管理方式不一樣。
棧編譯器自動管理,無需程序員手工控制;而堆空間的申請釋放工做由程序員控制,容易產生內存泄漏。對於棧來說,是由編譯器自動管理,無需咱們手工控制;對於堆來講,釋放工做由程序員控制,容易產生memory leak。空間大小:通常來說在32位系統下,堆內存能夠達到4G的空間,從這個角度來看堆內存幾乎是沒有什麼限制的。可是對於棧來說,通常都是有必定的空間大小的,例如,在VC6下面,默認的棧空間大小是1M。固然,這個值能夠修改。碎片問題:對於堆來說,頻繁的new/delete勢必會形成內存空間的不連續,從而形成大量的碎片,使程序效率下降。對於棧來說,則不會存在這個問 題,由於棧是先進後出的隊列,他們是如此的一一對應,以致於永遠都不可能有一個內存塊從棧中間彈出,在它彈出以前,在它上面的後進的棧內容已經被彈出,詳細的能夠參考數據結構。生長方向:對於堆來說,生長方向是向上的,也就是向着內存地址增長的方向;對於棧來說,它的生長方向是向下的,是向着內存地址減少的方向增加。分配方式:堆都是動態分配的,沒有靜態分配的堆。棧有2種分配方式:靜態分配和動態分配。靜態分配是編譯器完成的, 好比局部變量的分配。動態分配由malloca函數進行分配,可是棧的動態分配和堆是不一樣的,它的動態分配是由編譯器進行釋放,無需咱們手工實現。分配效率:棧是機器系統提供的數據結構,計算機會在底層對棧提供支持:分配專門的寄存器存放棧的地址,壓棧出棧都有專門的指令執行,這就決定了棧的效率比較高。堆則是C/C++函數庫提供的,它的機制是很複雜的,例如爲了分配一塊內存,庫函數會按照必定的算法(具體的算法能夠參考數據結構/操做系統)在堆內存中搜索可用的足夠大小的空間,若是沒有足夠大小的空間(多是因爲內存碎片太多),就有可能調用系統功能去增長程序數據段的內存空間,這樣就有機會分到足夠大小的內存,而後進行返回。顯然,堆的效率比棧要低得多。從這裏咱們能夠看到,堆和棧相比,因爲大量new/delete的使用,容易形成大量的內存碎片;因爲沒有專門的系統支持,效率很低;因爲可能引起用戶態和核心態的切換,內存的申請,代價變得更加昂貴。因此棧在程序中是應用最普遍的,就算是函數的調用也利用棧去完成,函數調用過程當中的參數,返回地址, EBP和局部變量都採用棧的方式存放。因此,咱們推薦你們儘可能用棧,而不是用堆。雖然棧有如此衆多的好處,可是因爲和堆相比不是那麼靈活,有時候分配大量的內存空間,仍是用堆好一些。不管是堆仍是棧,都要防止越界現象的發生(除非你是故意使其越界),由於越界的結果要麼是程序崩潰,要麼是摧毀程序的堆、棧結構,產生以想不到的結果。
(2)空間大小不一樣。
棧是向低地址擴展的數據結構,是一塊連續的內存區域。這句話的意思是棧頂的地址和棧的最大容量是系統預先規定好的,當申請的空間超過棧的剩餘空間時,將提示溢出。所以,用戶能從棧得到的空間較小。
堆是向高地址擴展的數據結構,是不連續的內存區域。由於系統是用鏈表來存儲空閒內存地址的,且鏈表的遍歷方向是由低地址向高地址。因而可知,堆得到的空間較靈活,也較大。棧中元素都是一一對應的,不會存在一個內存塊從棧中間彈出的狀況。
在Windows下,棧是向低地址擴展的數據結構,是一塊連續的內存的區域。這句話的意思是棧頂的地址和棧的最大容量是系統預先規定好的,在 WINDOWS下,棧的大小是2M(也有的說是1M,總之是一個編譯時就肯定的常數),若是申請的空間超過棧的剩餘空間時,將提示overflow。所以,能從棧得到的空間較小。堆:堆是向高地址擴展的數據結構,是不連續的內存區域。這是因爲系統是用鏈表來存儲的空閒內存地址的,天然是不連續的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限於計算機系統中有效的虛擬內存。因而可知,堆得到的空間比較靈活,也比較大。
(3)是否產生碎片。
對於堆來說,頻繁的malloc/free(new/delete)勢必會形成內存空間的不連續,從而形成大量的碎片,使程序效率下降(雖然程序在退出後操做系統會對內存進行回收管理)。對於棧來說,則不會存在這個問題。
(4)增加方向不一樣。
堆的增加方向是向上的,即向着內存地址增長的方向;棧的增加方向是向下的,即向着內存地址減少的方向。
(5)分配方式不一樣。
堆都是程序中由malloc()函數動態申請分配並由free()函數釋放的;棧的分配和釋放是由編譯器完成的,棧的動態分配由alloca()函數完成,可是棧的動態分配和堆是不一樣的,他的動態分配是由編譯器進行申請和釋放的,無需手工實現。
STACK: 由系統自動分配。例如,聲明在函數中一個局部變量 int b;系統自動在棧中爲b開闢空間。HEAP:須要程序員本身申請,並指明大小,在C中malloc函數。指向堆中分配內存的指針則多是存放在棧中的。
(6)分配效率不一樣。
棧是機器系統提供的數據結構,計算機會在底層對棧提供支持:分配專門的寄存器存放棧的地址,壓棧出棧都有專門的指令執行。堆則是C函數庫提供的,它的機制很複雜,例如爲了分配一塊內存,庫函數會按照必定的算法(具體的算法能夠參考數據結構/操做系統)在堆內存中搜索可用的足夠大的空間,若是沒有足夠大的空間(多是因爲內存碎片太多),就有須要操做系統來從新整理內存空間,這樣就有機會分到足夠大小的內存,而後返回。顯然,堆的效率比棧要低得多。
棧由系統自動分配,速度較快。但程序員是沒法控制的。
堆是由new分配的內存,通常速度比較慢,並且容易產生內存碎片,不過用起來最方便。
(7)申請後系統的響應
棧:只要棧的剩餘空間大於所申請空間,系統將爲程序提供內存,不然將報異常提示棧溢出。
堆:首先應該知道操做系統有一個記錄空閒內存地址的鏈表,當系統收到程序的申請時,會遍歷該鏈表,尋找第一個空間大於所申請空間的堆結點,而後將該結點從空閒結點鏈表中刪除,並將該結點的空間分配給程序。對於大多數系統,會在這塊內存空間中的首地址處記錄本次分配的大小,這樣,代碼中的delete語句才能正確的釋放本內存空間。因爲找到的堆結點的大小不必定正好等於申請的大小,系統會自動的將多餘的那部分從新放入空閒鏈表中。
(8)堆和棧中的存儲內容
棧:在函數調用時,第一個進棧的是主函數中後的下一條指令(函數調用語句的下一條可執行語句)的地址,而後是函數的各個參數,在大多數的C編譯器中,參數是由右往左入棧的,而後是函數中的局部變量。注意靜態變量是不入棧的。當本次函數調用結束後,局部變量先出棧,而後是參數,最後棧頂指針指向最開始存的地址,也就是主函數中的下一條指令,程序由該點繼續運行。棧中的內存是在程序編譯完成之後就能夠肯定的,不論佔用空間大小,仍是每一個變量的類型。
堆:通常是在堆的頭部用一個字節存放堆的大小。堆中的具體內容由程序員安排。
(9)存取效率的比較
1 char s1[] = "a"; 2 char *s2 = "b";
a是在運行時刻賦值的;而b是在編譯時就肯定的可是,在之後的存取中,在棧上的數組比指針所指向的字符串(例如堆)快。
(10)防止越界發生
不管是堆仍是棧,都要防止越界現象的發生(除非你是故意使其越界),由於越界的結果要麼是程序崩潰,要麼是摧毀程序的堆、棧結構,產生以想不到的結果,就算是在你的程序運行過程當中,沒有發生上面的問題,你仍是要當心,說不定何時就崩掉,那時候debug但是至關困難的
此程序顯示了數據存儲區域實例,在此程序中,使用了etext、edata和end3個外部全局變量,這是與用戶進程相關的虛擬地址。在程序源代碼中列出了各數據的存儲位置,同時在程序運行時顯示了各數據的運行位置,下圖所示爲程序運行過程當中各變量的存儲位置。
mem_add.c
1 /* mem_add.c演示了C語言中地址的分佈狀況 */ 2 3 #include <stdio.h> 4 #include <stdlib.h> 5 6 extern void afunc(void); 7 extern etext, edata, end; 8 9 int bss_var; /* 未初始化全局數據存儲在BSS區 */ 10 int data_var = 42; /* 初始化全局數據區域存儲在數據區 */ 11 #define SHW_ADDR(ID, I) printf("the %8s\t is at addr:%8x\n", ID, &I); /* 打印地址 */ 12 13 int main(int argc, char *argv[]) { 14 15 char *p, *b, *nb; 16 printf("Addr etext: %8x\t Addr edata %8x\t Addr end %8x\t\n", &etext, &edata, &end); 17 18 printf("\ntext Location:\n"); 19 SHW_ADDR("main", main); /* 查看代碼段main函數位置 */ 20 SHW_ADDR("afunc", afunc); /* 查看代碼段afunc函數位置 */ 21 printf("\nbss Location:\n"); 22 SHW_ADDR("bss_var", bss_var); /* 查看BSS段變量的位置 */ 23 printf("\ndata Location:\n"); 24 SHW_ADDR("data_var", data_var); /* 查看數據段變量的位置 */ 25 printf("\nStack Locations:\n"); 26 27 afunc(); 28 p = (char *)alloca(32); /* 從棧中分配空間 */ 29 if(p != NULL) { 30 SHW_ADDR("start", p); 31 SHW_ADDR("end", p+31); 32 } 33 34 b = (char *)malloc(32*sizeof(char)); /* 從堆中分配空間 */ 35 nb = (char *)malloc(16*sizeof(char)); /* 從堆中分配空間 */ 36 printf("\nHeap Locations:\n"); 37 printf("the Heap start: %p\n", b); /* 堆的起始位置 */ 38 printf("the Heap end: %p\n", (nb+16*sizeof(char))); /* 堆的結束位置 */ 39 printf("\nb and nb in Stack\n"); 40 41 SHW_ADDR("b", b); /* 顯示棧中數據b的位置 */ 42 43 SHW_ADDR("nb", nb); /* 顯示棧中數據nb的位置 */ 44 45 free(b); /* 釋放申請的空間 */ 46 free(nb); /* 釋放申請的空間 */ 47 }
afunc.c
1 /* afunc.c */ 2 #include <stdio.h> 3 #define SHW_ADDR(ID, I) printf("the %s\t is at addr:%p\n", ID, &I); /* 打印地址 */ 4 void afunc(void) { 5 static int long level = 0; /* 靜態數據存儲在數據段中 */ 6 int stack_var; /* 局部變量存儲在棧區 */ 7 8 if(++level == 5) 9 return; 10 11 printf("stack_var%d is at: %p\n", level, &stack_var); 12 SHW_ADDR("stack_var in stack section", stack_var); 13 SHW_ADDR("level in data section", level); 14 15 afunc(); 16 }
gcc mem_add.c afunc.c進行編譯而後執行輸出的可執行的文件,可獲得以下結果(本機有效):
而後能夠根據地址的大小來進行一個排序,並可視化:
若是運行環境不同,運行程序的地址與此將有差別,可是,各區域之間的相對關係不會發生變化。能夠經過readelf命令來查看可執行文件的詳細內容。
readelf -a a.out
來看一個問題,下面代碼的輸出結果是啥?
第一個文件code1.c
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 char* toStr() { 5 char *s = "abcdefghijk"; 6 return s; 7 } 8 9 int main(void) { 10 printf("%s\n", toStr()); 11 }
第二個文件code2.c
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 char* toStr() { 5 char s[] = "abcdefghijk"; 6 return s; 7 } 8 9 int main(void) { 10 printf("%s\n", toStr()); 11 }
其實我在用gcc編譯第二的時候已經有warning了:
第一個能夠正常輸出,而第二個要麼亂碼,要麼是空的。
兩段代碼都很簡單,輸出一段字符,類型不一樣,一個是char*字符串,一個是char[]數據。
結果:第一個正確輸出,第二個輸出亂碼。
緣由:在於局部變量的做用域和內存分配的問題,第一char*是指向一個常量,做用域爲函數內部,被分配在程序的常量區,直到整個程序結束才被銷燬,因此在程序結束前常量仍是存在的。而第二個是數組存放的,做用域爲函數內部,被分配在棧中,就會在函數調用結束後被釋放掉,這時你再調用,確定就錯誤了。
我發現了一個新的問題,若是你把這兩個文件合成一個的話,第二個其實能夠打印出正確的字符的,代碼以下:
1 /* toStr.c演示內存分配問題哦 */ 2 3 #include <stdio.h> 4 #include <stdlib.h> 5 6 char* toStr1() { 7 char *s = "abcdefghijklmn"; 8 return s; 9 } 10 11 char* toStr2() { 12 char s[] = "abcdefghijklmn"; 13 return s; 14 } 15 16 void printStr() { 17 int a[] = {1,2,3,4,5,6,7}; 18 } 19 20 int main(void) { 21 printf("調用toStr1()返回的結果: %s\n",toStr1()); 22 printf("調用toStr2()返回的結果: %s\n",toStr2()); 23 // printStr(); 24 exit(0); 25 26 }
不知道爲啥,第二個仍是能夠正常打印的。可是隻打印第二個,或者先打印第二個,而後在打印第一個的話,不輸出亂碼,卻是輸出空串。
顧名思義,局部變量就是在一個有限的範圍內的變量,做用域是有限的,對於程序來講,在一個函數體內部聲明的普通變量都是局部變量,局部變量會在棧上申請空間,函數結束後,申請的空間會自動釋放。而全局變量是在函數體外申請的,會被存放在全局(靜態區)上,知道程序結束後纔會被結束,這樣它的做用域就是整個程序。靜態變量和全局變量的存儲方式相同,在函數體內聲明爲static就可使此變量像全局變量同樣使用,不用擔憂函數結束而被釋放。
通常編譯器和操做系統實現來講,對於虛擬地址空間的最低(從0開始的幾K)的一段空間是未被映射的,也就是說它在進程空間中,但沒有賦予物理地址,不能被訪問。這也就是對空指針的訪問會致使crash的緣由,由於空指針的地址是0。至於爲何預留的不是一個字節而是幾K,是由於內存是分頁的,至少要一頁;另外幾k的空間還能夠用來捕捉使用空指針的狀況。
char *d = "hello" 中的a是指向第一個字符‘a'的一個指針;char s[20] = "hello" 中數組名a也是執行數組第一個字符'h'的指針。現執行下列操做:strcat(d, s)。把字符串加到指針所指的字串上去,出現段錯誤,本質緣由:*d="0123456789"存放在常量區,是沒法修的。而數組是存放在棧中,是能夠修改的。二者區別以下:
讀寫能力:char *a = "abcd"此時"abcd"存放在常量區。經過指針只能夠訪問字符串常量,而不能夠改變它。而char a[20] = "abcd"; 此時 "abcd"存放在棧。能夠經過指針去訪問和修改數組內容。
賦值時刻:char *a = "abcd"是在編譯時就肯定了(由於爲常量)。而char a[20] = "abcd"; 在運行時肯定
存取效率:char *a = "abcd"; 存於靜態存儲區。在棧上的數組比指針所指向字符串快。所以慢,而char a[20] = "abcd"存於棧上,快。另外注意:char a[] = "01234",雖然沒有指明字符串的長度,可是此時系統已經開好了,就是大小爲6-----'0' '1' '2' '3' '4' '5' '\0',(注意strlen(a)是不計'\0')