当前位置: > Linux服务器 > Linux系统 >

Linux内存分配中的堆和栈

时间:2014-12-21 19:46来源:linux.it.net.cn 作者:IT

1、什么是堆栈?

2、一道微软的笔试题。
3、自己写的两个关于堆栈的例子?
4、如何动态申请二维数组?

一、什么是堆栈?

1、内存分配

    一个由C/C++编译的程序占用的内存分为以下几个部分 
1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。 
2、堆区(heap) — 一般由程序员分配释放 , 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。 
3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域(BSS)。 - 程序结束后由系统释放 
4、文字常量区 — 常量字符串就是放在这里的。 程序结束后由系统释放 
5、程序代码区— 存放函数体的二进制代码。

    一个简单的例子

[html] view plaincopy
  1. //main.cpp   
  2. int a = 0;      /* 全局初始化区 */  
  3. char *p1;       /* 全局未初始化区 */  
  4. void main()   
  5. {   
  6.     int b;              /* 栈 */   
  7.     char s[] = "abc";   /* 栈 */  
  8.     char *p2;           /* 栈 */  
  9.     char *p3 = "123456";    /* 123456在常量区,p3在栈上 */  
  10.     static int c =0;        /* 全局(静态)初始化区 */  
  11.       
  12.     /* 分配得来得10和20字节的区域就在堆区 */  
  13.     p1 = (char *)malloc(10);   
  14.     p2 = (char *)malloc(20);   
  15.       
  16.     /* 123456放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方 */  
  17.     strcpy(p1, "123456");  
  18. }  

2、堆栈的理论知识

申请方式 

stack:   由系统自动分配。 例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间 
heap:   需要程序员自己申请,并指明大小,在c中malloc函数  如p1 = (char *)malloc(10);  在C++中用new运算符如p2 = new char[10]; 但是注意p1、p2本身是在栈中的。 

申请后系统的响应 

栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。 
堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时, 会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的 delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。

申请大小的限制 

栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在 WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。 
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。

二、一道微软笔试题

[html] view plaincopy
  1. 6 Which of following C++ code is correct?  (E)  
  2. (A) int f()  
  3.     {  
  4.         int* a = new int(3);  
  5.         return a;  
  6.     }  
  7. (B) int* f()  
  8.     {  
  9.         int a[3] = {1, 2, 3};  
  10.         return a;  
  11.     }  
  12. (C) vector<int> f()  
  13.     {  
  14.         vector<int> v(3);  
  15.         return v;  
  16.     }  
  17. (D) void f(int *ret)  
  18.     {  
  19.         int a[3] = {1, 2, 3};  
  20.         ret = a;  
  21.         return;  
  22.     }  
  23. (E) none of above  

根据上面的实例,我们来分析一下堆栈空间的使用情况。
(A) 是错误的,原因是函数声明中返回的是int,而实际返回的a是int*。现在让我们思考一下,如果在函数的声明修改为:int *f()。这样返回出去的指针指向的空间是否可用?  我们知道,使用new申请的动态空间是在堆上申请的,其申请和释放都是由程序员来操作的,所以在函数返回时,它不会随着函数的返回而释放。这一点与栈空间不同,在函数返回时,在该函数内部的申请的栈空间都将被系统释放。为了保证程序的安全性,该函数返回的栈空间指针是不可用的。但是实际上,我们可以使用任何一个栈空间上的指针去访问栈空间的。
 
(B)是错误的,原因上面提到了,int a[3] = {1, 2, 3} 是局部变量,其位于栈空间。在函数返回时,我们返回了指针a,如果在返回指针之后,为了安全着想,我们将不使用该指针。下面我将使用实例来说明不安全性。
[html] view plaincopy
  1. #include <stdio.h>  
  2.   
  3. int* f()  
  4. {  
  5.     int a[3] = {4, 5, 6};  
  6.     return a;  
  7. }  
  8.   
  9. int main()  
  10. {  
  11.     int *c = f();  
  12.     printf("%d, %d\n", c[0], c[1]);  
  13.     printf("%d, %d\n", c[0], c[1]);  
  14.     return true;  
  15. }  

这段程序的输出结果为:
4,5 
1245056, 4201335
很显然,在返回指针c后,直接输出c[0]c[1],此时栈空间还么有被覆盖,所以可以正确的输出4,5。但是执行了第一个输出函数之后,可能覆盖了原来栈空间的内容,再进行输出的时候,就输出的是garbage value.因此这是不安全的。
(C) 是错误的,原因和B一样的
(D) 是错误的,原因有二,一是函数内申请的是栈空间。二是 ret是形参,所以当函数返回时,值不会发生变化。举个例子来说明:
[html] view plaincopy
  1. #include <stdio.h>  
  2.   
  3. void f(int *ret)  
  4. {  
  5.     int a[3] = {1, 2, 3};  
  6.     ret = a;  
  7.     return;  
  8. }  
  9.   
  10. int main()  
  11. {  
  12.     int *c = NULL;  
  13.     f(c);  
  14.     printf("%x\n", c);  
  15.     return true;  
  16. }  
结果是0,也就是说,c的值没有发生变化。

3、自己写的两个关于堆栈的例子?

为了说明上述选项D的原因,我自己写了一个程序来说明情况。
[html] view plaincopy
  1. #include <stdio.h>  
  2. #include <malloc.h>  
  3.   
  4. void Swap1(int a, int b)  
  5. {  
  6.     int temp;  
  7.     temp = a;  
  8.     a = b;  
  9.     b = temp;  
  10. }  
  11.   
  12. void Swap2(int* a, int* b)  
  13. {  
  14.     int temp;  
  15.     temp = *a;  
  16.     *a = *b;  
  17.     *b =*a;  
  18. }  
  19.   
  20. void RequestHeap1(int* array)  
  21. {  
  22.     printf("RequestHeap1 申请动态空间之前 %d\n", array);  
  23.     <span style="white-space:pre">  </span>array = (int *)malloc(sizeof(int) * 4);  
  24.     printf("RequestHeap1 申请动态空间之后 %d\n", array);  
  25. }  
  26.   
  27. void RequestHeap2(int** array)  
  28. {  
  29.     printf("RequestHeap2 申请动态空间之前 %d\n", *array);  
  30.     *array = (int *)malloc(sizeof(int) * 4);  
  31.     printf("RequestHeap2 申请动态空间之后 %d\n", *array);  
  32. }  
  33.   
  34. void DeleteHeap(int* array)  
  35. {  
  36.     if (array != NULL)  
  37.     {  
  38.         free(array);  
  39.         array = NULL;  
  40.     }  
  41.       
  42. }  
  43.   
  44.   
  45. int main()  
  46. {  
  47.     int tmp1 = 3, tmp2 =5;  
  48.     Swap1(tmp1, tmp2);  
  49.     printf("tmp1 = %d, tmp2 = %d\n", tmp1, tmp2);  
  50.       
  51.     Swap2(&tmp1, &tmp2);  
  52.     printf("tmp1 = %d, tmp2 = %d\n", tmp1, tmp2);  
  53.   
  54.     <span style="white-space:pre">  </span>int *array1 = NULL;  
  55.     <span style="white-space:pre">  </span>RequestHeap1(array1);  
  56.     printf("RequestHeap1 Return : %d\n", array1);   
  57.     DeleteHeap(array1);  
  58.   
  59.     int *array2 = NULL;  
  60.     RequestHeap2(&(array2));  
  61.     printf("RequestHeap2 Return : %d\n", array2);  
  62.     DeleteHeap(array2);  
  63.   
  64.     return true;  
  65. }  
 
程序运行结果如下:
在void Swap1(int a, int b)中, a 与 b 是形参,在函数内形参的值虽然发生改变,但是函数执行后,并不会改变实参的值。也就是在执行Swap1(tmp1, tmp2)时,将实参tmp1和tmp2的值赋予形参a和b,在函数内改变形参的值,但是并不会影响实参值,所以执行Swap1(tmp1, tmp2)之后,tmp1和tmp2的值并不发生变化。
void Swap2(int * a, int*  b)中,a 与 b是int *类型,也是形参,同样,函数执行后不会改变实参的值。也就是执行Swap2(&tmp1,&tmp2)时,将tmp1和tmp2的指针赋值给形参a和b,然而在这个函数中,我们是利用指针来改变该指针指向的值。即temp = *a; *a = *b; *b = temp;
同理,void Request1(int *array),在执行RequestHeap1(array1)时,将array1赋值给形参array,在函数中对形参array进行各种操作之后,并不会直接改变实参的值。所以我们发现在执行该函数后,array1仍为0。
而void Request2(int** array),再执行RequestHeap2(&(array2))时,将array2的地址赋值给形参array,在函数中对*array进行操作,实际它并没有改变&(array2),但是该函数改变了*array2的值。
不知道大家明白了没?

4、如何动态申请二维数组

[html] view plaincopy
  1. #include <stdio.h>  
  2. #include <malloc.h>  
  3.   
  4. void RequestHeap2(int*** array, int m, int n)  
  5. {  
  6.     *array = (int**) malloc(sizeof(int *) * m);  
  7.     for (int i = 0; i < m; i++)  
  8.     {  
  9.         (*array)[i] = (int*)malloc(sizeof(int) * n);  
  10.     }  
  11. }  
  12.   
  13. int** RequestHeap1(int m, int n)  
  14. {  
  15.     int** array;  
  16.     array = (int**) malloc(sizeof(int *) * m);  
  17.     for (int i = 0; i < m; i++)  
  18.     {  
  19.         array[i] = (int*)malloc(sizeof(int) * n);  
  20.     }  
  21.     return array;  
  22. }  
  23.   
  24. void DeleteHeap(int** array, int m, int n)  
  25. {  
  26.     for (int i = 0; i < m; i++)  
  27.     {  
  28.         free(array[i]);  
  29.         array[i] = NULL;  
  30.     }  
  31.     free(array);  
  32.     array = NULL;  
  33. }  
  34.   
  35. void main()  
  36. {  
  37.     int** ppArray1 = NULL;  
  38.     int** ppArray2 = NULL;  
  39.     int m, n;  
  40.     scanf("%d, %d", &m, &n);  
  41.   
  42.     ppArray1 = RequestHeap1(m, n);  
  43.     ppArray1[0][0] = 5;  
  44.     printf("%d, %x\n", ppArray1[0][0], ppArray1);  
  45.     DeleteHeap(ppArray1, m, n);  
  46.   
  47.     RequestHeap2(&(ppArray2), m, n);  
  48.     ppArray2[0][0] = 5;  
  49.     printf("%d, %x\n", ppArray2[0][0], ppArray2);  
  50.     DeleteHeap(ppArray2, m, n);  
  51. }  
 
我们使用了两种方法,
(1) int** RequestHeap1(int m, int n),在函数内部申请动态二维数组空间,并返回指针。
(2)void RequestHeap2(int*** array, int m, int n), 通过参数int *** array传递一个二维数组的首地址的指针,来完成二维数组的创建
运行结果如下:
运行成功!
(责任编辑:IT)
------分隔线----------------------------