c語言基礎學習08_內存管理

============================================================================= 涉及到的知識點有: 一、內存管理、作用域、自動變量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語言源代碼裏面某些程序直接這樣寫的。

山寨機(機器配置比較低)上寫程序,需要好好考慮內存的使用,比如嵌入式系統中寫程序。

=============================================================================

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章