結構體中動態內存的管理(malloc和free)

C語言中內存的管理主要是依據malloc和free實現的,其中malloc主要是實現內存的分配,而free則是實現內存的釋放。雖然這是我們已經很熟悉的,但是還是存在一些問題。特別是當結構體中存在指針的情況下,各種問題也就會展現出來。
其中最大的問題是:結構體中指針變量沒有指向一塊合法的內存空間,就對指針參數進行操作,這也是很多C語言程序員經常犯的錯誤。

簡單的實例如下:
  1. struct student
  2. {
  3.         char *name;
  4.         int score;
  5. }stu,*pstu;

  6. int main()
  7. {
  8.         strcpy(stu.name,"Jimy");
  9.         stu.score = 99;


  10.         strcpy(pstu->name,"Jimy");
  11.         pstu->score = 99;
  12. }


這種代碼是新手經常犯的錯誤,其中的主要錯誤是指針變量沒有指向一塊內存空間,其中包括ptest沒有指向一塊內存空間,同時結構體中的指針變量name也沒有指向一塊內存空間。

這種代碼一般都會編譯通過,但是運行過程中會發生新手編程經常出現的段錯誤Segmentation fault (core dumped),我通過gdb對程序進行調試發現了存在的一些問題。其中stu.name中的內容是0x0,也就是地址0x0。這樣我就知道了0x0爲什麼會發生段錯誤了,因爲在Linux中進程都有一個獨立的虛擬存儲空間4G,但是其中在最底部的0x0是沒有映射的,具體的參看進程的存儲器映射關係。0x0並沒有映射,這樣發生段錯誤也就不奇怪了。

也就是說指針變量裏存儲的地址值並不是一個我們需要的值,爲了指向一塊內存空間,因此需要採用malloc分配一塊內存空間。

改寫上面的代碼實現內存的分配。
  1. int main()
  2. {
  3.         /*創建一塊內存空間,並讓stu.name指向這塊內存空間*/
  4.         stu.name = (char *)malloc(20*sizeof(char));
  5.         /*實現字符串的複製過程*/
  6.         strcpy(stu.name,"Jimy");
  7.         stu.score = 99;

  8.         /*創建一塊內存空間,並讓pstu指向這塊內存空間*/
  9.         pstu = (struct student *)malloc(sizeof(struct student));
  10.         /*創建一塊內存空間,並讓pstu->name指向這塊內存空間*/
  11.         pstu->name = (char *)malloc(20*sizeof(char));
  12.         /*實現字符串的複製過程*/
  13.         strcpy(pstu->name,"Jimy");
  14.         pstu->score = 99;
  15.     
  16.         return 0;
  17. }

這樣補充以後的代碼就爲指針變量添加了指向的對象,由於是採用malloc動態申請的存儲空間,那麼這段存儲空間是分配在堆中,而不是在棧中,如果是在被調用函數中申請內存空間,那麼在函數返回後該內存空間並不會釋放。

  1. Breakpoint 1, main () at TestStructPoint.c:21
  2. 21 stu.name = (char *)malloc(20*sizeof(char));
  3. Missing separate debuginfos, use: debuginfo-install glibc-2.12.90-17.i686
  4. (gdb) p stu     ----stu中的內容
  5. $1 = {name = 0x0, score = 0}
  6. (gdb) p stu.name  ----stu.name其中的內容是0x0,也就是指向0x0
  7. $2 = 0x0
  8. (gdb) c
  9. Continuing.
  10. Breakpoint 2, main () at TestStructPoint.c:25
  11. 25 strcpy(stu.name,"Jimy");
  12. (gdb) p stu.name   -----stu.name其中的內容不再是0x0,而是一個地址值,該地值中的內容爲空
  13. $3 = 0x804a008 ""
  14. (gdb) c
  15. Continuing.
  16. Breakpoint 3, main () at TestStructPoint.c:26
  17. 26 stu.score = 99;
  18. (gdb) p stu.name    -----stu.name中存儲的地址的內容發生了變化。
  19. $4 = 0x804a008 "Jimy"
  20. (gdb) c
  21. Continuing.
  22. Breakpoint 4, main () at TestStructPoint.c:29
  23. 29 pstu = (struct student *)malloc(sizeof(struct student));
  24. (gdb) p pstu        ----pstu指向的地址也是0x0
  25. $5 = (struct student *) 0x0
  26. (gdb) c
  27. Continuing.
  28. Breakpoint 5, main () at TestStructPoint.c:32
  29. 32 pstu->name = (char *)malloc(20*sizeof(char));
  30. (gdb) p pstu    ----pstu指向的內存地址發生了改變,不再是0x0
  31. $6 = (struct student *) 0x804a020
  32. (gdb) c
  33. Continuing.
  34. Breakpoint 6, main () at TestStructPoint.c:35
  35. 35 strcpy(pstu->name,"Jimy");
  36. (gdb) p pstu
  37. $7 = (struct student *) 0x804a020
  38. (gdb) p pstu->name   ----pstu->name中的地址也不再是0x0,而是一個非零的地址值
  39. $8 = 0x804a030 ""
  40. (gdb) c
  41. Continuing.
  42. Breakpoint 7, main () at TestStructPoint.c:36
  43. 36 pstu->score = 99;
  44. (gdb) p pstu->name
  45. $9 = 0x804a030 "Jimy"  ----pstu->name指向地址中的內容發生改變
  46. (gdb) c
  47. Continuing.
  48. Program exited normally.

根據上面的調試可以知道,指針變量在定義過程中沒有初始化爲NULL,則指針變量指向的地址就是0x0,而在Linux中的進程虛擬存儲器映射中地址0x0並沒有映射,因此會出現錯誤。因此結構體中的指針變量一定要指向一塊具體的存儲空間之後才能進行相應的操作。同時其他的指針也必須指向相應的地址以後再操作。

但是分配完地址後還需要在相應操作結束後釋放分配的存儲器,不然會造成浪費,以及內存的泄漏。這也是很多程序員忘記完成的工作。

內存的釋放採用free函數即可,free函數是將分配的這塊內存與指針(malloc返回的指針)之間的所有關係斬斷,指針變量P中存儲的地址(這塊內存的起始地址)值也沒有發生變化,同時存儲器中存儲的內容也並沒有發生改變,改變的只是指針對這塊內存地址的所有權問題。但是該起始地址所在內存中的數據內容已經沒法使用了,即時採用其他的指針也不能訪問。如果下一次調用malloc函數可能會在剛纔釋放的區域創建一個內存空間,由於釋放以後的存儲空間的內容並沒有改變(我是參考書上的,但我認爲free後存儲器中的內容是發生變化的,後面的調試可以說明這個問題,只是不知道發生什麼變化,我也只是猜測,但是不要訪問這個存儲空間的內容是最安全的),這樣可能會影響後面的結果,因此需要對創建的內存空間進行清零操作(防止前面的操作影響後面),這通常採用memset函數實現,具體參看memset函數。還有指針變量P中存儲的地址值並沒有改變,由於指針P沒有對這個地址的訪問權限,程序中對P的引用都可能導致錯誤的產生,造成野指針,因此最後還需要將指針P指向NULL,避免野指針的產生。當然也需要對創建是否成功需要檢測,但這裏我暫時不考慮這些錯誤的處理。

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #include<string.h>

  4. struct student
  5. {
  6.         char *name;
  7.         int score;
  8. }stu,*pstu;


  9. int main()
  10. {
  11.         /*爲name分配指向的一段內存空間*/
  12.         stu.name = (char *)malloc(20*sizeof(char));
  13.         memset(stu.name,0,20*sizeof(char));

  14.         strcpy(stu.name,"Jimy");
  15.         stu.score = 99;

  16.         /*爲pstu分配指向的一段內存空間*/
  17.         pstu = (struct student *)malloc(sizeof(struct student));
  18.         memset(pstu,0,sizeof(struct student));

  19.         /*爲name分配指向的一段內存空間*/
  20.         pstu->name = (char *)malloc(20*sizeof(char));
  21.         memset(pstu->name,0,20*sizeof(char));

  22.         strcpy(pstu->name,"Jimy");
  23.         pstu->score = 99;

  24.         /*採用另外的指針訪問分配的存儲空間,測試內存中內容是否改變*/
  25.         char *= stu.name;
  26.         char *p1 = (char *)0x804a008;//具體的地址值 
  27.         char *ppstu = pstu->name;
  28.         char *pp = (char *)0x804a030;//具體的地址值

  29.         /*釋放的順序要注意,pstu->name必須在pstu釋放之前釋放,
  30.          *如果pstu先釋放,那麼pstu->name就不能正確的訪問。
  31.         */
  32.         free(pstu->name);
  33.         free(stu.name);
  34.         free(pstu);

  35.         /*爲了防止野指針產生*/
  36.         pstu->name = NULL;
  37.         stu.name = NULL;
  38.         pstu = NULL;
  39.         p = NULL;
  40.         ppstu = NULL;

  41.         return 0;
  42. }
下面的全部是調試結果,根據調試結果說明問題:
  1. (gdb) r
  2. Starting program: /home/gong/program/cprogram/TestStructPoint 

  3. Breakpoint 1, main () at TestStructPoint.c:14
  4. 14stu.name = (char *)malloc(20*sizeof(char));
  5. Missing separate debuginfos, use: debuginfo-install glibc-2.12.90-17.i686
  6. (gdb) p stu
  7. $1 = {name = 0x0, score = 0}
  8. (gdb) p stu.name
  9. $2 = 0x0
  10. (gdb) c
  11. Continuing.

  12. Breakpoint 2, main () at TestStructPoint.c:17
  13. 17strcpy(stu.name,"Jimy");
  14. (gdb) p stu.name
  15. $3 = 0x804a008 ""
  16. (gdb) c
  17. Continuing.

  18. Breakpoint 3, main () at TestStructPoint.c:21
  19. 21pstu = (struct student *)malloc(sizeof(struct student));
  20. (gdb) p stu.name
  21. $4 = 0x804a008 "Jimy"
  22. (gdb) p stu
  23. $5 = {name = 0x804a008 "Jimy", score = 99}
  24. (gdb) p pstu
  25. $6 = (struct student *) 0x0
  26. (gdb) c
  27. Continuing.

  28. Breakpoint 4, main () at TestStructPoint.c:24
  29. 24pstu->name = (char *)malloc(20*sizeof(char));
  30. (gdb) p pstu
  31. $7 = (struct student *) 0x804a020
  32. (gdb) p pstu->name
  33. $8 = 0x0
  34. (gdb) c
  35. Continuing.

  36. Breakpoint 5, main () at TestStructPoint.c:27
  37. 27strcpy(pstu->name,"Jimy");
  38. (gdb) p pstu->name
  39. $9 = 0x804a030 ""
  40. (gdb) c
  41. Continuing.

  42. Breakpoint 6, main () at TestStructPoint.c:31
  43. 31char *p = stu.name;
  44. (gdb) p pstu->name
  45. $10 = 0x804a030 "Jimy"
  46. (gdb) p *pstu
  47. $11 = {name = 0x804a030 "Jimy", score = 99}
  48. (gdb) p p
  49. $12 = 0x854ff4 "|M\205"
  50. (gdb) c
  51. Continuing.

  52. Breakpoint 7, main () at TestStructPoint.c:32
  53. 32char *p1 = (char *)0x804a008;//具體的地址值
  54. (gdb) p p1
  55. $13 = 0x855ca0 ""
  56. (gdb) c
  57. Continuing.

  58. Breakpoint 8, main () at TestStructPoint.c:33
  59. 33char *ppstu = pstu->name;
  60. (gdb) p p1
  61. $14 = 0x804a008 "Jimy"
  62. (gdb) p ppstu
  63. $15 = 0x855ca0 ""
  64. (gdb) c
  65. Continuing.

  66. Breakpoint 9, main () at TestStructPoint.c:34
  67. 34char *pp = (char *)0x804a030;//具體的地址值
  68. (gdb) p ppstu
  69. $16 = 0x804a030 "Jimy"
  70. (gdb) p pp
  71. $17 = 0x804826a "__libc_start_main"
  72. (gdb) c
  73. Continuing.

  74. Breakpoint 10, main () at TestStructPoint.c:37
  75. 37free(pstu->name);
  76. (gdb) p pp
  77. $18 = 0x804a030 "Jimy"
  78. (gdb) p pstu->name
  79. $19 = 0x804a030 "Jimy"
  80. (gdb) c
  81. Continuing.

  82. Breakpoint 11, main () at TestStructPoint.c:38
  83. 38free(stu.name);
  84. (gdb) p pstu->name
  85. $20 = 0x804a030 ""
  86. (gdb) c
  87. Continuing.

  88. Breakpoint 12, main () at TestStructPoint.c:39
  89. 39free(pstu);
  90. (gdb) p stu.name
  91. $21 = 0x804a008 "(\240\004\b"
  92. (gdb) p pstu
  93. $22 = (struct student *) 0x804a020
  94. (gdb) p *pstu
  95. $23 = {name = 0x804a030 "", score = 99}
  96. (gdb) c
  97. Continuing.

  98. Breakpoint 13, main () at TestStructPoint.c:41
  99. 41pstu->name = NULL;
  100. (gdb) p *pstu
  101. $24 = {name = 0x0, score = 99}
  102. (gdb) p pstu->name
  103. $25 = 0x0
  104. (gdb) c
  105. Continuing.

  106. Breakpoint 14, main () at TestStructPoint.c:47
  107. 47return 0;
  108. (gdb) p p1
  109. $26 = 0x804a008 "(\240\004\b"
  110. (gdb) p pp
  111. $27 = 0x804a030 ""
  112. (gdb) p pstu
  113. $28 = (struct student *) 0x0
  114. (gdb) p pstu->name
  115. Cannot access memory at address 0x0
  116. (gdb) 

具體的調試過程說明了其中很多的問題,根據其中的結果可以知道,free結束以後指針變量P(malloc返回)中存儲的地址值並沒有改變,但是通過該地值已經不能訪問之前的分配的存儲空間。我採用其他的指針(直接賦值地址以及指針賦值)訪問得到的結果也不是正確的值,雖然這不能說明地址中的數據改變了,但說明對釋放以後的存儲空間再通過其他方法訪問不會得到正確的值,但是內存空間中存在值,並不一定是0,因此每一次malloc都清零是必要的。防止野指針也是非常必要的,減少程序錯誤的概率。

最後說明一下,鏈表作爲結構體的衍生產物,鏈表的結構體中就有指針變量,因此一定草採用malloc等分配好內存塊以後,再對鏈表進行操作,不然都會導致不同問題的產生。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章