1. 程式人生 > >對堆區、棧區、全域性靜態區的理解

對堆區、棧區、全域性靜態區的理解

1、棧區:

#include<stdio.h>

int main(void) {
	int x = 2;    				//在棧上面申請一個int型別長度的空間
	int y[] = {1,2,3};   		//在棧上面申請一個int型別陣列長度為3的一段空間
	char s[] = {"132423"};		//在棧上面申請一個char型別陣列長度為6的一段空間
	printf("x_address = %p\n", &x);
	printf("y_address = %p\n", &y);
	printf("s_address = %p\n", s);
	
return 0; }

可以通過輸出地址看一下上述幾個變數的地址變化。


比如上面程式碼中變數的定義都是在棧上面建立的,在棧上面申請變數由編譯器自動分配釋放,存放函式的引數值、區域性變數的值等,定義的變量出了作用範圍之後會由編譯器自動釋放,棧的大小一般是2M或者1M,一個int是4個位元組,2M的大小就是2M=2*1024K = 2 * 1024*1024 B,這麼多個位元組。 棧裡面的地址是向低地址擴充套件的,可以想象成從棧底向棧頂消耗棧空間。在棧裡面分配空間的優點是:分配速度快,不用擔心用完了回收的事情。缺點就是:整個棧的空間大小太小了,拿2M的大小來算,就能放int型別的資料2 * 1024*1024 / 4 = 524288個,所以棧的地方很適合函式區域性變數,不必擔心回收的問題,不適合申請很大的空間。

補充一個在棧裡面動態申請的函式alloca,是今天在書《c專家程式設計》中看到的,此函式申請位元組長度為t的空間,返回一個指標,是在棧上面申請的,用完之後自動回收。

但是這種方法並不適用於那些比建立它們的函式生命期更長的結構。如果物件的生命期在該函式結束前便已終止,這種在棧上面的動態分配是一個不錯的選擇。

void *alloca(size_t t);
int count;
scanf("%d", &count);
int *array1 = (int *)alloca(sizeof(int) * count);

2、堆區

#include<stdio.h>
#include<malloc.h>

int main(void) {
	int *array1  = NULL;
	int count = 5;
	int i;
 
	array1 = (int *)calloc(sizeof(int), count);   //在堆上面申請一段長度為sizeof(int)*count空間給array1指標
	for(i = 0; i < count; i++) {
		printf("%p\n", &array1[i]);
	}

	free(array1);

	return 0;
}

堆區的申請是動態的,可通過使用者傳入的引數申請指定長度的記憶體空間,堆的記憶體也是很大,可以看成當前後臺空閒的記憶體大小,可以通過new,malloc,calloc,realloc申請,回收的方式是delete或者free。堆區的地址是從低向高擴充套件的。

每次需要申請一段記憶體空間的時候,會在當前的記憶體裡面找一段連續的記憶體合適大小分配給這個指標,至於分配的演算法有幾種:最佳適應、最差適應、首次適應。

最佳適應演算法和最差適應演算法是反過來的,這兩種演算法對當前空間的記憶體塊都是按照大小排序,最差適應是分配了最大的記憶體,最佳適應是分配了剛好合適的記憶體塊。而首次適應不用對空閒區排序,只要找到一個滿足需求大小的空閒分割槽,就分配。

通常動態在堆上面分配的記憶體,比如一個長度為10的int型別陣列,應該佔用位元組數是40個,但實際上是申請了40 的下一個2的次方數,也就是64,它會圓整為下一個大於申請數量的2的整數次方,申請的長度是64位元組長度。16個int的長度。所以一旦發生記憶體洩漏,忘記手動釋放申請的空間,造成洩漏的記憶體要比忘記釋放的那個的資料結構更大。 

在堆上申請空間的優點:堆的空間大、只要有足夠的空閒空間可申請任意大小的空間。

缺點:動態申請的空間必須記得寫釋放函式操作,否則就會造成嚴重而且很難察覺的記憶體洩漏!!野指標!!

array1 = (int *)calloc(sizeof(int), 10);

3、全域性/靜態區

static修飾或者全域性的變數放在這裡,全域性靜態區的空間大小和堆的大小差不多。

其中初始化的變數和未初始化變數存放的不是一個位置。未初始化的變數放在未初始化資料區(BBS),這個區域用來存放程式中未初始化的全域性/靜態變數的一塊記憶體區域。

對於這一部分的理解,可以把靜態變數和全域性變數歸為一類理解,不過在c裡面的語法要求,static可以用來修飾一個區域性變數,java裡面的static修飾變數必須是在全域性下的,是一個類的成員,函式裡面static修飾變數一定報錯。

#include<stdio.h>

int x = 2;   	//全域性變數初始化過的
int y;			//全域性變數未初始化


int main(void) {
	static int z = 2;			//靜態變數初始化過的
	static int z2;				//靜態變數未初始化的

	printf("x: %p\n", &x);
	printf("y: %p\n", &y);
	printf("z: %p\n", &z);
	printf("z2: %p\n", &z2);

	return 0;
}

4、字元常量區

char *s1 = "1234";  //常量 不可更改

這種方式申請的是一個字元常量,不可更改,相同的內容在記憶體中只佔一份空間,上述就是一個字元常量,字元常量區在全域性靜態區的一部分,但沒有和全域性靜態變數挨在一起。

下面的程式碼中雖然有多個字串,s1,s2,s3,但是這三個字串在記憶體中的地址絕對是相同的,並且字串是不可更改的,這個常量是字元常量,和const修飾的變數還不一樣,就算const int a = 2這樣子這個a還是在棧上而不是在字元常量區。

#include<stdio.h>

char *s3 = "1234";   //存放字元常量區 相同內容只佔一份記憶體

int main(void) {
	char *s1 = "1234";
      char *s2 = "1234";
printf("s1 = %p\n", s1);printf("s2 = %p\n", s2);printf("s3 = %p\n", s3);return 0;}


5、程式程式碼區:程式程式碼區,用於存放程式的二進位制程式碼的空間,這一部分不是很理解。


對比各記憶體區 

#include<stdio.h>
#include<malloc.h>

int x1;					//未初始化的全域性變數 在bss區
int x2 = 2;				//初始化的全域性變數 在全域性靜態區
char *s3 = "12345678";   	//存放在字元常量區 並且相同內容只佔一份記憶體 並且發現了在全域性區放了一個指標 指向字元常量區 佔四個位元組

// 在c裡面未初始化的全域性變數 和初始化的全域性變數是放在一塊的
// 而在c++裡面初始化和非初始化變數不在一起

int main(void) {
	static int x3 = 3;		//初始化的靜態變數 在全域性靜態區 和上面的x2應該是挨在一起的
	static int x5;			//未初始化的靜態變數 在bss區
	char *s1 = "12345678";		//存放在字元常量區 並且相同內容只佔一份記憶體
	char *s2 = "12345678";		//存放在字元常量區 並且相同內容只佔一份記憶體
  	const  int x4 = 4;  	//這個也是放在棧區的 而不是在字元常量區
  	int *array = NULL;
  	int i;
        int str[] = {12345678"};        //這是在棧上的 和上面的字串沒有關係
  	array = (int *)calloc(sizeof(int), 5);  	//在堆上申請一段長度為sizeof(int)*5的記憶體給array指標

	printf("x1 = %p\n", &x1);
	printf("x2 = %p\n", &x2);
	printf("x3 = %p\n", &x3);
	printf("x4 = %p\n", &x4);
	printf("x5 = %p\n", &x5); 

	printf("s1 = %p\n", s1);
	printf("s2 = %p\n", s2);
	printf("s3 = %p\n", s3);

	for(i =0; i < 5; i++) {
		printf("%p ", array++);
	}

	free(array);			//釋放在堆上面申請的空間
	array = NULL;

	return 0;
}

輸出結果:




關於全域性靜態變數未初始化的,是要放在一起未初始化的區域,但至於為啥不是連續的,中間的記憶體的內容應該是放了其他的變數引用什麼的。