============================================================================= 涉及到的知識點有: 一、內存管理、作用域、自動變量auto、寄存器變量register、代碼塊作用域內的靜態變量、代碼塊作用域外的靜態變量。
二、內存佈局、代碼區 code、靜態區 static、棧區 stack、堆區 heap。
三、堆的分配和釋放、c語言幾個使用堆內存的庫函數:malloc函數、free函數、calloc函數、realloc函數、 函數的返回值爲指針類型01_(即函數的返回值是一個地址)、函數的返回值爲指針類型02_、 堆的使用例子:通過堆空間實現動態大小變化的字符數組、函數calloc 和 函數realloc 的使用案例、 通過函數形參爲一級指針時,在函數內部分配堆內存的錯誤案例、通過函數形參爲二級指針時,在函數內部分配堆內存的正確案例。 ============================================================================= ============================================================================= 一、內存管理
實際上內存管理是通過指針來管理的。要想寫出高質量的代碼,必須要了解計算機的內存。
1、作用域
一個c語言變量的作用域可以是代碼塊作用域、函數作用域、文件作用域。
代碼塊:是指大括號{...}之間的一段代碼。
同一個作用域不能有同名變量,但不同作用域變量名稱可以相同。 打比方:同一個家裏面的人的名字不能一樣。
linux下示例代碼如下:
1 #include <stdio.h> 2 3 int c = 2; //文件作用域,因爲它屬於這個文件本身,並不在任何一個函數裏面。 4 5 int main() 6 { 7 int a = 0; //函數作用域。 8 { 9 int b = 1; //代碼塊作用域。 10 } 11 12 return 0; 13 } 14 -------------------------------------- 15 //3個作用域的名字可以一樣,不衝突的。因爲它們的作用域不一樣! 16 17 int a = 2; //文件作用域,因爲它屬於這個文件本身,並不在任何一個函數裏面。 18 19 int main() 20 { 21 int a = 0; //函數作用域。 22 { 23 int a = 1; //代碼塊作用域。 24 } 25 26 return 0; 27 }
============================================================================= 2、自動變量auto
一般情況下,代碼塊內部定義的變量都是自動變量。當然也可以顯示的使用auto關鍵字, 所有的自動變量的生命週期就是變量所屬的大括號。
例如: auto signed int a = 0; //定義了一個自動變量。二者等價 int a = 0; ============================================================================= 3、寄存器變量register
通常變量在內存當中,如果能把變量放到cpu的寄存器裏面,代碼的執行效率會更高。
例如: register int a = 0; //定義了一個寄存器變量。 ============================================================================= 4、代碼塊作用域內的靜態變量
靜態變量是指在程序執行期間一直不改變的變量,一個代碼塊內部的靜態變量只能被這個代碼內部訪問。
例如: static int i = 0; //定義了一個靜態變量。 ----------------------------------------------------------------------------- linux下示例代碼如下:
1 #include <stdio.h> 2 3 void test() 4 { 5 auto signed int a = 0; //等價於 int a = 0; 6 7 a++; 8 printf("a = %d\n", a); 9 } 10 11 int main() 12 { 13 register int a = 0; //寄存器變量。 14 static int b = 0; //靜態變量。 15 16 int i; 17 for (i = 0; i < 10; i++) 18 { 19 test(); 20 } 21 22 return 0; 23 } 24 輸出的結果爲: 25 a = 1 26 a = 1 27 a = 1 28 a = 1 29 a = 1 30 a = 1 31 a = 1 32 a = 1 33 a = 1 34 a = 1
----------------------------------------------------------------------------- 靜態變量在程序剛加載進內存的時候就出現了,所以它和定義靜態變量的大括號無關, 一直到程序結束的時候才從內存中消失,同時靜態變量的值只初始化一次。
linux下示例代碼如下:
1 #include <stdio.h> 2 3 void test() 4 { 5 static int a = 0; //等價於 int a = 0; 6 7 a++; 8 printf("a = %d\n", a); 9 } 10 11 int main() 12 { 13 register int a = 0; //寄存器變量。 14 static int b = 0; //靜態變量。 15 16 int i; 17 for (i = 0; i < 10; i++) 18 { 19 test(); 20 } 21 22 return 0; 23 } 24 輸出的結果爲: 25 a = 1 26 a = 2 27 a = 3 28 a = 4 29 a = 5 30 a = 6 31 a = 7 32 a = 8 33 a = 9 34 a = 10
============================================================================= 5、代碼塊作用域外的靜態變量
代碼塊之外的靜態變量在程序執行期間一直存在,但只能被定義這個變量的文件訪問, 代碼塊之外的靜態變量只能在定義這個變量的文件中使用,在其他文件中不能被訪問。
因爲全局變量的名字是不能相同的,這樣會帶來一個什麼問題? 因爲一個項目往往是多個人寫的,每個人都定義自己的全局變量,最後代碼合併時會出錯。 但是static定義的全局變量在不同文件中的名字是可以相同的。 ============================================================================= 6、全局變量
全局變量的存儲方式和靜態變量相同,但可以被多個文件訪問,定義在代碼塊之外的變量就是全局變量。
全局變量即使不在同一個文件裏面,也不能重名。 -------------------------------------- linux下示例代碼如下:
mem3.c文件內容如下:
1 #include <stdio.h> 2 3 extern int a; //聲明瞭一個變量a。extern的意思是:外面的,外來的。 4 5 //void test(); //簡便寫法。 6 extern void test(); //聲明瞭一個函數。更嚴謹的寫法。意思是說:該函數是外部函數,在其他地方定義了。 7 8 int main() 9 { 10 test(); 11 printf("a = %d\n", a ); 12 13 return 0; 14 }
-------------------------------------- mem4.c文件內容如下:
1 int a = 1; //a是一個全局變量。 2 3 void test() 4 { 5 a++; 6 }
輸出結果爲: root@iZ2zeeailqvwws5dcuivdbZ:~/1/01/內存管理# gcc -o a mem3.c mem4.c root@iZ2zeeailqvwws5dcuivdbZ:~/1/01/內存管理# a a = 2 root@iZ2zeeailqvwws5dcuivdbZ:~/1/01/內存管理#
----------------------------------------------------------------------------- 靜態全局變量只能在定義它的文件內部訪問,對於文件外部其他的文件是不可以使用的(訪問的)。 如果在代碼塊之外的一個變量或者函數,c語言默認都是全局的。除非寫了個static就改變了它的類型了。 ============================================================================= ============================================================================= 二、內存佈局
1、代碼區 code
程序被操作系統加載到內存的時候,所有的可執行代碼都加載到代碼區,也叫代碼段, 這塊內存是不可以在運行期間修改的。
代碼區中所有的內容在程序加載到內存的時候就確定了,運行期間不可以修改,只可以執行。 ============================================================================= 2、靜態區 static
靜態區是程序加載到內存的時候就確定了,程序退出的時候從內存消失。
所有的全局變量和靜態變量在程序運行期間都佔用內存。
所有的全局變量以及程序中的靜態變量都存儲到靜態區。 -------------------------------------- linux下示例代碼如下:
1 #include <stdio.h> 2 3 int a = 0; //在靜態區存儲。 4 static int b = 1; //在靜態區存儲,與 int b = 1; 的地址是一樣的。 5 6 int main() 7 { 8 static int c = 2; //在靜態區存儲。 9 auto int d = 3; //自動變量在棧中存儲。 10 int e = 4; //自動變量在棧中存儲。 11 12 printf("%p, %p, %p, %p, %p\n", &a, &b, &c, &d, &e); //0x60104c, 0x601040, 0x601044, 0x7ffe3fddd1c0, 0x7ffe3fddd1c4 13 14 return 0; 15 }
============================================================================= 3、棧區 stack
棧是一種先進後出的內存結構,所有的 自動變量、函數的形參、函數的返回值 都是由編譯器自動放入棧中。
當一個自動變量超出其作用域時,會自動從棧中彈出。
不同的系統下棧的大小是不一樣的,即使是相同的系統,棧的大小也是不一樣的。一般來講棧不會很大,單位是多少K字節。 windows系統下的程序在編譯的時候就可以指定棧的大小,linux系統下棧的大小是可以通過環境變量來設置的。 ============================================================================= 4、堆區 heap
堆和棧一樣,也是一種在程序運行過程中可以隨時修改的內存區域,但是沒有棧那樣先進後出的順序。
堆的使用較複雜些,堆內存空間的申請和釋放需要我們手動通過代碼來完成。
對是一個大容器,它的容量要遠遠大於棧,但是在c語言中,堆內存空間的申請和釋放需要我們手動通過代碼來完成。
============================================================================= ============================================================================= 三、堆的分配和釋放
c語言幾個使用堆內存的庫函數,需要用到頭文件 #include <stdlib.h>。 ============================================================================= 1、malloc函數
void *malloc(size_t size); malloc函數的功能是:在堆中分配指定大小的內存,單位是:字節。 函數返回值是:void *指針。(無類型指針) ============================================================================= 2、free函數 void free(void *ptr); free函數的功能是:負責在堆中釋放有malloc分配的內存。 參數是:ptr爲malloc返回堆中的內存地址。 ============================================================================= 3、calloc函數 void *calloc(size_t nmemb, size_t size); calloc函數與malloc函數的功能類似,都是負責在堆中分配內存。 malloc只負責分配,但不負責清理內存。 calloc分配內存的同時把內存清空(即置0)。
第一個參數是:所需分配內存的單元數量;第二個參數是:每隔內存單元堆的大小(單位:字節)。 ============================================================================= 4、realloc函數 void *realloc(void *ptr, size_t size); realloc函數的功能是:重新分配用malloc函數或calloc函數在堆中分配內存空間的大小。 第一個參數是:ptr爲之前用malloc或calloc分配的堆內存地址,第二個參數是:重新分配內存的大小,單位:字節。 realloc函數成功則返回重新分配的堆內存地址,失敗返回NULL。 若擦數ptr = NULL,那麼realloc和malloc的功能一樣了。
realloc也不會自動清理增加後的內存,也需要手動清理。
如果指定地址後面有連續的空間,那麼realloc就會在已有地址的基礎上增加內存。 如果指定的地址後面沒有多餘的空間,那麼realloc會重新分配新的連續內存,把進內存的值拷貝到新的內存,並同時釋放舊內存。 (這是realloc的智能之處) ----------------------------------------------------------------------------- linux下示例代碼如下:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 5 int main() 6 { 7 //一個棧裏面的自動指針變量s指向了一個堆的地址空間。 8 auto char *s; 9 s = malloc(10); //在堆中申請了(分配了)10個字節的空間,又因爲返回值是void *,所以該句爲在堆中申請了(分配了)10個char的空間。 10 11 strcpy(s, "abcd"); 12 printf("%s\n", s); //abcd 13 free(s); //釋放堆中的內存。不釋放的話就會一直佔着! 14 15 s = malloc(100); //因爲s是自動指針變量,釋放後可以重新使用,這個時候s又重新指向了一個新的堆地址空間。 16 free(s); //free(s);並不是把自動指針變量s釋放了,而是釋放了s所指向的那塊堆內存空間。 17 18 //一個程序的棧大小是有限的,如果一個數組特別大,有可能會導致棧溢出,所以不要在棧裏面定義太大的數組。 19 //int a[100000000]; //定義了一個數組,這個數組在內存的棧區裏面。 20 //a[99999999] = 0; //程序編譯沒有問題,但是程序運行出現Segmentation fault(段錯誤) 21 22 printf("%lu\n", sizeof(int)); //4 23 24 //當一個數組特別大時,我們可以使用堆。 25 int *p = malloc(100000000 * sizeof(int)); 26 p[99999999] = 0; 27 free(p); 28 29 return 0; 30 }
----------------------------------------------------------------------------- 什麼時候在棧中使用一個數組呢?又什麼時候在堆中使用一個數組呢?
1、如果使用一個特別大的數組,那麼需要把數組放入堆中,而不是棧。
2、如果一個數組在定義的時候,大小不能確定,那麼適合用堆,而不是棧。
3、如果malloc分配的內存忘記free,那麼會發生內存泄漏,這個也是初學者最容易犯的錯誤。
malloc分配的空間裏面的值是隨機的,不會自動置0。
堆到底有多大呢?它取決於物理內存,取決於操作系統本身,並不取決於你的程序。如下代碼:
//可以根據用戶的輸入在堆中分配大小不同的數組。 linux下示例代碼如下:
1 #include <stdio.h> 2 #include <string.h> 3 #include <stdlib.h> 4 5 int main() 6 { 7 int i; 8 scanf("%d", &i); 9 10 int *p = malloc(i * sizeof(int)); 11 12 int a; 13 for (a = 0; a < i; a++) 14 { 15 printf("%d\n", p[i]); 16 } 17 18 free(p); 19 20 return 0; 21 }
============================================================================= 函數的返回值爲指針類型01_(即函數的返回值是一個地址)
linux下示例代碼如下:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 5 int *test() 6 { 7 int a = 10; //a是auto,是局部變量。在棧裏面。空間會自動釋放。 8 return &a; //出現編譯警告:warning: function returns address of local variable [-Wreturn-local-addr] 9 //警告:函數返回局部變量的地址[-Wreturn-local-addr] 10 } 11 12 int *test1() 13 { 14 int *p = malloc(1 * sizeof(int)); //在堆中申請了(分配了)4個字節的空間,也即一個int的空間。在堆裏面。空間不會自動釋放。 15 *p = 10; 16 return p; 17 } 18 19 char *test2() 20 { 21 char a[100] = "hello"; //a是auto,是局部變量。 22 return a; //同樣出現編譯警告:warning: function returns address of local variable [-Wreturn-local-addr] 23 //警告:函數返回局部變量的地址[-Wreturn-local-addr] 24 } 25 26 char *test3() 27 { 28 char *a = malloc(100); //在堆中申請了(分配了)100個字節的空間。也即100個char的空間。 29 strcpy(a, "hello"); 30 return a; 31 } 32 //由test()和test2()可知:在函數內部不能直接返回一個auto類型變量的地址,因爲auto類型變量的地址都是自動的,一旦該函數執行完後,這個地址就無效了。 33 34 //----------------------------------------------------------------------------- 35 char *test4(char *arg) 36 { 37 return arg; 38 } 39 40 char *test5(char *arg) 41 { 42 return &arg[5]; //返回下標爲5的成員變量的地址。 43 } 44 45 char *test6() 46 { 47 char *p = malloc(100); 48 *p = 'a'; //等價於 p[0] = 'a'; 49 *(p + 1) = 'b'; //等價於 p[1] = 'b'; 50 *(p + 2) = '0'; //等價於 p[2] = '0'; 或 p[2] = 0; 或 *(p + 2) = 0; 51 52 return p; 53 } 54 55 char * test7() 56 { 57 char *p = malloc(100); 58 *p = 'a'; 59 p++; 60 *p = 'b'; 61 p++; 62 *p = 0; 63 64 return p; 65 } 66 67 int main01() 68 { 69 //int *p = test(); //編譯時出現警告,因爲test執行完後內部的自動變量a已經不在內存了,所以p指向了一個無效的地址,但是這塊內存的內容還在。也即變成了野指針了。 70 int *p = test1(); //是通過堆內存分配函數進行內存分配的,函數test1執行完後,內存不會自動釋放的。 71 printf("%d\n", *p); //10 72 free(p); 73 74 //char *p1 = test2(); //編譯時同樣出現警告,因爲test執行完後內部的自動變量a已經不在內存了,所以p指向了一個無效的地址,也即變成了野指針了。 75 //printf("%s\n", p1); //忽略警告後,執行輸出結果不可知。 76 //free(p1); 77 78 char *p2 = test3(); 79 printf("%s\n", p2); //hello 80 free(p2); 81 82 return 0; 83 } 84 85 int main() 86 { 87 char a[100] = "hahahaha"; //定義了一個auto自動變量是數組變量,在棧裏面。 88 char *p; 89 p = test4(a); //該句執行後:等價於 p = a;或 p = &a[0]; p指向的是棧裏面的地址。 90 printf("%s\n", p); //hahahaha 即從角標爲0的元素開始輸出。 91 92 p = test5(a); //該句等價於 p = &a[5],即從數組a的角標爲5的元素開始。 93 printf("%s\n", p); //aha 即從數組a的角標爲5的元素開始輸出。 94 95 p = test6(); 96 printf("%s\n", p); //ab 97 free(p); 98 99 p = test7(); 100 printf("%s\n", p); //編譯沒有問題,執行出現問題。 101 free(p); 102 103 return 0; 104 }
輸出結果爲:
root@iZ2zeeailqvwws5dcuivdbZ:~/1/01/內存管理# gcc -o a mem8.c root@iZ2zeeailqvwws5dcuivdbZ:~/1/01/內存管理# a hahahaha aha ab *** Error in `a': free(): invalid pointer: 0x0000000000cc8422 *** ======= Backtrace: ========= /lib/x86_64-linux-gnu/libc.so.6(+0x777e5)[0x7f517e1cb7e5] /lib/x86_64-linux-gnu/libc.so.6(+0x8037a)[0x7f517e1d437a] /lib/x86_64-linux-gnu/libc.so.6(cfree+0x4c)[0x7f517e1d853c] a[0x400916] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0)[0x7f517e174830] a[0x400599] ======= Memory map: ======== 00400000-00401000 r-xp 00000000 fd:01 1050518 /root/1/01/內存管理/a 00600000-00601000 r--p 00000000 fd:01 1050518 /root/1/01/內存管理/a 00601000-00602000 rw-p 00001000 fd:01 1050518 /root/1/01/內存管理/a 00cc8000-00ce9000 rw-p 00000000 00:00 0 [heap] 7f5178000000-7f5178021000 rw-p 00000000 00:00 0 7f5178021000-7f517c000000 ---p 00000000 00:00 0 7f517df3e000-7f517df54000 r-xp 00000000 fd:01 1180169 /lib/x86_64-linux-gnu/libgcc_s.so.1 7f517df54000-7f517e153000 ---p 00016000 fd:01 1180169 /lib/x86_64-linux-gnu/libgcc_s.so.1 7f517e153000-7f517e154000 rw-p 00015000 fd:01 1180169 /lib/x86_64-linux-gnu/libgcc_s.so.1 7f517e154000-7f517e314000 r-xp 00000000 fd:01 1185266 /lib/x86_64-linux-gnu/libc-2.23.so 7f517e314000-7f517e514000 ---p 001c0000 fd:01 1185266 /lib/x86_64-linux-gnu/libc-2.23.so 7f517e514000-7f517e518000 r--p 001c0000 fd:01 1185266 /lib/x86_64-linux-gnu/libc-2.23.so 7f517e518000-7f517e51a000 rw-p 001c4000 fd:01 1185266 /lib/x86_64-linux-gnu/libc-2.23.so 7f517e51a000-7f517e51e000 rw-p 00000000 00:00 0 7f517e51e000-7f517e544000 r-xp 00000000 fd:01 1185244 /lib/x86_64-linux-gnu/ld-2.23.so 7f517e737000-7f517e73a000 rw-p 00000000 00:00 0 7f517e740000-7f517e743000 rw-p 00000000 00:00 0 7f517e743000-7f517e744000 r--p 00025000 fd:01 1185244 /lib/x86_64-linux-gnu/ld-2.23.so 7f517e744000-7f517e745000 rw-p 00026000 fd:01 1185244 /lib/x86_64-linux-gnu/ld-2.23.so 7f517e745000-7f517e746000 rw-p 00000000 00:00 0 7ffce8530000-7ffce8551000 rw-p 00000000 00:00 0 [stack] 7ffce85c5000-7ffce85c7000 r--p 00000000 00:00 0 [vvar] 7ffce85c7000-7ffce85c9000 r-xp 00000000 00:00 0 [vdso] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] Aborted (中止) root@iZ2zeeailqvwws5dcuivdbZ:~/1/01/內存管理#
錯誒原因如下圖:指針位移後free的問題說明
============================================================================= 函數的返回值爲指針類型02_
linux下示例代碼如下:
1 #include <stdio.h> 2 3 char *test() 4 { 5 static char a[100] = "hello"; 6 7 return a; 8 } 9 10 char *test1() 11 { 12 static char a[100] = "hello"; 13 char *p = a; 14 p++; 15 16 return p; 17 } 18 19 const char *test2() 20 { 21 const char *s = "hello"; //該意思是將s指向一個常量的地址,常量在程序運行期間是一直有效的。 22 23 return s; 24 } 25 26 const char *test3() //test2() 和 test3() 是一樣的! 27 { 28 return "hello world"; 29 } 30 31 char *test4() 32 { 33 return "haha"; //返回的是常量地址。而函數定義的卻是變量地址。類型不符。 34 } 35 36 int main() 37 { 38 char *str = test(); 39 printf("%s\n", str); //hello 40 41 char *str1 = test1(); 42 printf("%s\n", str1); //ello 43 44 const char *str2 = test2(); 45 printf("%s\n", str2); //hello 46 47 const char *str3 = test3(); 48 printf("%s\n", str3); //hello world 49 50 const char *str4 = test4(); 51 printf("%s\n", str4); //haha 函數定義的地址和返回的地址類型不符! 52 53 char *str4 = test4(); 54 str4[0] = 'a'; 55 printf("%s\n", str4); //編譯沒有問題,但執行出現段錯誤!因爲:常量區和靜態區類似,程序運行期間有效,但常量區是隻讀的,不能修改。 56 57 return 0; 58 }
============================================================================= 堆的使用例子:通過堆空間實現動態大小變化的字符數組
linux下示例代碼如下:
1 #include <stdio.h> 2 #include <string.h> 3 #include <stdlib.h> 4 5 int main01() 6 { 7 char a[10] = "hello"; //此時的棧不夠用了或者棧用的很不靈活了。 8 char b[10] = "haha"; 9 10 strcat(a, b); 11 printf("%s\n", a); //用strcat的時候要注意,第一個字符串一定要有足夠的空間容納第二個字符串。 12 13 return 0; 14 } 15 16 int main() 17 { 18 char a[] = "hello"; 19 char b[] = "hahahahahahahahahahaha"; 20 char *p = malloc(strlen(a) + strlen(b) + 1); 21 22 strcpy(p, a); 23 strcat(p, b); 24 printf("%s\n", p); //hellohahahahahahahahahahaha 25 free(p); 26 27 return 0; 28 }
============================================================================= 函數calloc 和 函數realloc 的使用案例:
linux下示例代碼如下:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 5 //現在用malloc或者calloc已經分配了10個int,如果想擴大或者縮小這塊內存,怎麼辦?用realloc。 6 //注意:用realloc增加的空間也不會自動清0。 7 int main01() 8 { 9 char *s1 = calloc(10, sizeof(char)); //在堆中分配了10個char空間。 10 char *s2 = calloc(10, sizeof(char)); 11 12 strcpy(s1, "123456789"); 13 strcpy(s2, "abcdef"); 14 15 s1 = realloc(s1, strlen(s1) + strlen(s2) + 1); //根據s1和s2的實際長度擴充s1的大小。 16 strcat(s1, s2); 17 18 printf("%s\n", s1); //123456789abcdef 19 20 free(s1); 21 free(s2); 22 23 return 0; 24 } 25 26 //不用realloc函數來實現擴大或者縮小內存。 27 int main02() 28 { 29 char *s1 = calloc(10, sizeof(char)); //在堆中分配了10個char空間。 30 char *s2 = calloc(10, sizeof(char)); 31 32 strcpy(s1, "123456789"); 33 strcpy(s2, "abcdef"); 34 35 char *tmp = malloc(strlen(s1) + strlen(s2) + 1); 36 37 strcpy(tmp, s1); 38 free(s1); 39 strcat(tmp, s2); 40 free(s2); 41 42 s1 = tmp; 43 free(s1); 44 printf("%s\n", s1); //123456789abcdef 45 46 return 0; 47 } 48 49 //malloc的智能體現:如果指定的地址後面有連續的空間,那麼就會在已有的地址的基礎上增加內存, 50 //如果指定的地址後面沒有空間,那麼realloc會重新分配新的連續內存,把舊內存的值拷貝到新內容,同時釋放舊內存。 51 int main() 52 { 53 char *s1 = malloc(100); 54 char *p = realloc(s1, 5000000); 55 56 if (s1 == p) 57 { 58 printf("在原有的基礎上增加內存\n"); 59 } 60 else 61 { 62 printf("不是在原有的基礎上增加內存\n"); 63 } 64 free(p); 65 66 return 0; 67 }
realloc的智能體現如下圖所示:
============================================================================= 通過函數形參爲一級指針時,在函數內部分配堆內存的錯誤案例
linux下示例代碼如下:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 5 void test(char *s) 6 { 7 strcpy(s, "hello"); 8 } 9 10 void test1(char *s) 11 { 12 s = calloc(10, 1); 13 strcpy(s, "hello"); 14 } 15 16 int main01() 17 { 18 char *p = calloc(10, 1); //堆中分配了10個char 19 test(p); 20 21 printf("%s\n", p); //hello 22 free(p); 23 24 return 0; 25 } 26 27 int main() 28 { 29 char *p = NULL; 30 test1(p); 31 32 printf("%s\n", p); //編譯沒有問題,但執行出現Segmentation fault (core dumped)(段錯誤)。 33 free(p); 34 35 return 0; 36 }
通過函數形參爲一級指針時,在函數內部分配堆內存的錯誤案例的圖解如下圖所示:
============================================================================= 通過函數形參爲二級指針時,在函數內部分配堆內存的正確案例
linux下示例代碼如下:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 5 void test(char **s) 6 { 7 *s = calloc(10, 1); 8 strcpy(*s, "hello"); 9 } 10 11 int main() 12 { 13 char *p = NULL; 14 test(&p); 15 16 printf("%s\n", p); 17 free(0); 18 19 return 0; 20 }
通過函數形參爲二級指針時,在函數內部分配堆內存的正確案例的圖解如下圖所示:
============================================================================= windows系統分配內存的最小單位說明
vs2017下示例代碼如下:
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 int main() 5 { 6 while (1) 7 { 8 char *s = malloc(1024); 9 getchar(); //這個小函數的作用是:讓程序執行到這裏暫停一下。 10 } 11 return 0; 12 }
在windows系統下 任務管理器/詳細信息 下查看內存變化:
304K 308K 312K 316K ......
得出結論: windows系統的每次堆變化是4K字節。 如果你需要1K的空間,操作系統會給4K; 如果你需要5K的空間,操作系統會給8K。 4K就是windows內存的最小頁。內存是按照頁來區分的。不是按照字節來區分的,不同的操作系統頁的大小是不同的。 頁的優點是:效率提升;缺點是:浪費了一些內存。
char *s = malloc(4 * 1024); //我們會發現:有些c語言源代碼裏面某些程序直接這樣寫的。
在山寨機(機器配置比較低)上寫程序,需要好好考慮內存的使用,比如嵌入式系統中寫程序。
=============================================================================