1. 程式人生 > >《C語言深度剖析》學習筆記----記憶體管理(2)

《C語言深度剖析》學習筆記----記憶體管理(2)

開始陸續的發一下唐老師視訊的筆記吧,順便帶一些正衝哥書的的內容。不能一下都發出來,因為內容發多了自己也受不了,而且發的都是學習視訊時候的一些筆記,可能會有一些問題不是很清晰。

先說一下C語言中的記憶體管理。

1.動態記憶體分配

①原因:程式執行過程中,很有可能需要一些額外的記憶體空間。
②動態記憶體從哪裡來,還給誰?
  這塊是記憶體是系統專門預留出來的,給程式動態的分配和動態的歸還的。
  當free函式的引數為空的時候,那麼我們的free什麼事都不做了。
  clloc和realloc的用法見截圖:

動態記憶體是從堆上分配空間的,具體形式以int為例 int *a = (int*)malloc (5*(sizeof(int)))。

2.棧,堆,靜態儲存區
①沒有棧就沒有函式,就沒有區域性變數。函式是依賴於棧的存在而存在的。
  這個棧不是資料結構中的棧,是記憶體棧。
  後進先出的原則沒有改變。

  函式的棧是從兩面向中間增長的。
  每一個函式的呼叫在記憶體中都會產生活動記錄,這個活動記錄包括函式的引數和返回地址等等,所以可以說這個活動記錄就是在棧上面建立的。


  按照老唐的話說,一個棧應該包含的有如下資料
  (1)返回地址(子函式ebp在返回ebp的時候應該指向的位置)
  (2)old ebp
  (3)資料(包含函式中的所有資料)(這也是子函式和子函式esp指向的位置)
  棧的增長方式:從棧底一直向上增長。


②因為棧的資料在函式執行結束之後就會被釋放了,沒有傳遞到函式的外部,所以產生了程式中的堆。
堆中申請的記憶體在程式主動釋放前一直有效。堆空間就是為了動態記憶體分配而產生的。
堆空間必須要通過申請才可以得到。
堆管理的方法有很多。老唐講的連結串列法很清晰。
③程式靜態儲存區
程式靜態儲存區在程式執行的時候就已經開闢出來了,同時在程式執行結束的時候釋放。
靜態程式區的大小在程式編譯的時候就已經規定好了。
程式的靜態儲存區主要用於儲存程式執行的全域性變數和靜態變數。

這三個儲存區是程式中所用的絕大數區域。

3.程式的記憶體佈局。
程式中除了棧,堆,靜態儲存區以外還有其他的儲存區麼?
可執行程式的佈局:
(1)可執行程式的檔案頭:作業系統來讀這個可執行程式的檔案頭,看一下這個檔案頭裡的資訊是不是系統想要的資訊。
(2).text段,這裡面儲存的是主函式和子函式的執行內容。
(3).data段,這裡面儲存的是申請並且賦值的全域性變數,以及申請並且賦值的靜態變數。
(4).bss段,這裡面儲存的是申請但是沒有賦值的全域性變數,以及申請並沒有賦值的靜態變數。
通過上面的四個段,沒有找到儲存申請並且賦值的區域性變數的地方也沒有申請但是沒有賦值的區域性變數的地方。因為區域性變數是儲存在棧空間中的。

 程式檔案的佈局是程式可執行檔案的佈局,並不是程式執行過程中的佈局,堆疊和靜態儲存區才是程式執行過程(程序)的佈局。
text段、data段和bss段在程式執行的時候(程序)會被對映到程序空間中。

 棧和堆是要等到程式執行之後由作業系統進行分配的。

這裡的棧可以理解成為一個彈性的東西,所以在程式執行的時候對映過來的段和隨著棧的行為來行動。
 靜態儲存區在程式編譯的時候就已經開始分配空間了,所以靜態儲存區在程式執行的時候依然可以對應一部分程式的可執行檔案的空間。函式的地址對應的是程式text段中的某個地址。

4.野指標通常是因為指標變數儲存的值不是一個合法記憶體地址造成的。
  在C中沒有任何手段來判別一個指標是否是野指標。

①區域性指標變數沒有被初始化。

#include <stdio.h>

	struct student
	{
		char *name ;
		int no ;
	};
	int main()
	{
		struct student s; 
		//這個strcpy函式是把後面指標變數的記憶體內容賦給前面指標變數的內容。但是前面的指標變數指向的記憶體並不確定,所以造成了野指標。
		strcpy(s.name,"haha");
		s.no = 10;
		printf ("%s,%d",s.name,s.no);
	}

解決辦法:

#include <stdio.h>
	#include <string.h>
	struct student
	{
		char *name  ;
		int no ;
	};
	int main()
	{
		struct student s; 
		s.name = (char*)malloc(sizeof(char)*20);
		strcpy(s.name,"haha");
		s.no = 10;
		printf ("%s,%d",s.name,s.no);
	}

上面程式少了一個free(s.name).

②指標釋放後程序就歸還了這片空間,既然歸還了那麼就不可以再使用這篇空間了。
     使用已經釋放後的指標。
  例程:

#include <stdio.h>
	#include <string.h>
	#include <stdlib.h>

	int f(char*i)
	{
		printf ("%s\n",i);
		free (i);
	}
	int main()
	{
		char *p = (char*)malloc(sizeof(char)*5);

		strcpy(p, "qwertyui");

		f(p);
		printf ("%s\n",p);
	}

解決辦法:

 #include <stdio.h>
	#include <string.h>
	#include <stdlib.h>

	int f(char*i)
	{
		printf ("%s\n",i);
		//free (i);
		return 0;
	}

	int main()
	{
		char *p = (char*)malloc(sizeof(char)*10);

		strcpy(p, "qwertyui");

		f(p);
		
		printf ("%s\n",p);

		free (p);

		return 0;
	}

③指標所指向的變數在指標之前被銷燬。

5.記憶體操作中的經典錯誤
①非法記憶體操作
②記憶體分配成功但是並沒有初始化

例程;

#include <stdio.h>
	int main()
	{
		char *p = (char *)malloc(10);
		printf(p);
		free (p);
		return 0;
	}

③陣列越界
④記憶體洩露
⑤多次釋放指標----誰申請誰釋放。
⑥使用已經釋放的記憶體。

6.老唐自己的交通規則
①使用malloc之後應該立刻判斷返回值是否為空。可以杜絕操作0地址裡面的內容,這個地址是為作業系統使用的。

例程:

#include<stdio.h>
	int main()
	{
		int *p = (int*)malloc(sizeof(int)*5);
		
		if (p !== NULL)
		{
			.....
		}
		free (p);
		return 0;
	}

②牢記陣列長度,防止越界操作。----使用柔性陣列。
③動態申請的操作必須和釋放操作相匹配。
④free指標以後,被free的指標要賦值為空。