• Linux_内存分配中的堆和栈


    1、什么是堆栈?

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

    一、什么是堆栈?

    1、内存分配

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

        一个简单的例子

    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。因此,能从栈获得的空间较小。 
    堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。

    二、一道微软笔试题

    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,如果在返回指针之后,为了安全着想,我们将不使用该指针。下面我将使用实例来说明不安全性。
    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是形参,所以当函数返回时,值不会发生变化。举个例子来说明:
    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的原因,我自己写了一个程序来说明情况。
    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 = 3tmp2 =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、如何动态申请二维数组

    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传递一个二维数组的首地址的指针,来完成二维数组的创建
    运行结果如下:

    运行成功!


    作者:沙漏哟
    出处:计算机的未来在于连接
    本文版权归作者和博客园共有,欢迎转载,请留下原文链接
    微信随缘扩列,聊创业聊产品,偶尔搞搞技术
  • 相关阅读:
    java冒泡排序
    正则表达式手册
    简单介绍事务
    Java经典编程30题(中)
    Java经典编程30题(下)
    负载均衡-Nginx
    react入门
    Mybatis && Mybatis-plus
    java设计模式-委派模式
    java8新特性
  • 原文地址:https://www.cnblogs.com/yeahwell/p/5226048.html
Copyright © 2020-2023  润新知