内存分配中的堆和栈

时间:2021-08-01 13:07:16

转载请标注:http://blog.csdn.net/zgh1988/article/details/7470166

1、什么是堆栈?

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

一、什么是堆栈?

1、内存分配

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

    一个简单的例子

//main.cpp 
int a = 0;/* 全局初始化区 */
char *p1;/* 全局未初始化区 */
void main()
{
int b;/* 栈 */
char s[] = "abc";/* 栈 */
char *p2;/* 栈 */
char *p3 = "123456";/* 123456在常量区,p3在栈上 */
static int c =0;/* 全局(静态)初始化区 */

/* 分配得来得10和20字节的区域就在堆区 */
p1 = (char *)malloc(10);
p2 = (char *)malloc(20);

/* 123456放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方 */
strcpy(p1, "123456");
}

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

二、一道微软笔试题

6 Which of following C++ code is correct?  (E)
(A) int f()
{
int* a = new int(3);
return a;
}
(B) int* f()
{
int a[3] = {1, 2, 3};
return a;
}
(C) vector<int> f()
{
vector<int> v(3);
return v;
}
(D) void f(int *ret)
{
int a[3] = {1, 2, 3};
ret = a;
return;
}
(E) none of above

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

int* f()
{
int a[3] = {4, 5, 6};
return a;
}

int main()
{
int *c = f();
printf("%d, %d\n", c[0], c[1]);
printf("%d, %d\n", c[0], c[1]);
return true;
}

这段程序的输出结果为:
4,5 1245056, 4201335很显然,在返回指针c后,直接输出c[0]c[1],此时栈空间还么有被覆盖,所以可以正确的输出4,5。但是执行了第一个输出函数之后,可能覆盖了原来栈空间的内容,再进行输出的时候,就输出的是garbage value.因此这是不安全的。(C) 是错误的,原因和B一样的(D) 是错误的,原因有二,一是函数内申请的是栈空间。二是 ret是形参,所以当函数返回时,值不会发生变化。举个例子来说明:
#include <stdio.h>

void f(int *ret)
{
int a[3] = {1, 2, 3};
ret = a;
return;
}

int main()
{
int *c = NULL;
f(c);
printf("%x\n", c);
return true;
}
结果是0,也就是说,c的值没有发生变化。

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

为了说明上述选项D的原因,我自己写了一个程序来说明情况。
#include <stdio.h>
#include <malloc.h>

void Swap1(int a, int b)
{
int temp;
temp = a;
a = b;
b = temp;
}

void Swap2(int* a, int* b)
{
int temp;
temp = *a;
*a = *b;
*b =*a;
}

void RequestHeap1(int* array)
{
printf("RequestHeap1 申请动态空间之前 %d\n", array);
array = (int *)malloc(sizeof(int) * 4);
printf("RequestHeap1 申请动态空间之后 %d\n", array);
}

void RequestHeap2(int** array)
{
printf("RequestHeap2 申请动态空间之前 %d\n", *array);
*array = (int *)malloc(sizeof(int) * 4);
printf("RequestHeap2 申请动态空间之后 %d\n", *array);
}

void DeleteHeap(int* array)
{
if (array != NULL)
{
free(array);
array = NULL;
}

}


int main()
{
int tmp1 = 3, tmp2 =5;
Swap1(tmp1, tmp2);
printf("tmp1 = %d, tmp2 = %d\n", tmp1, tmp2);

Swap2(&tmp1, &tmp2);
printf("tmp1 = %d, tmp2 = %d\n", tmp1, tmp2);

int *array1 = NULL;
RequestHeap1(array1);
printf("RequestHeap1 Return : %d\n", array1);
DeleteHeap(array1);

int *array2 = NULL;
RequestHeap2(&(array2));
printf("RequestHeap2 Return : %d\n", array2);
DeleteHeap(array2);

return true;
}

程序运行结果如下:内存分配中的堆和栈
在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、如何动态申请二维数组

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

void RequestHeap2(int*** array, int m, int n)
{
*array = (int**) malloc(sizeof(int *) * m);
for (int i = 0; i < m; i++)
{
(*array)[i] = (int*)malloc(sizeof(int) * n);
}
}

int** RequestHeap1(int m, int n)
{
int** array;
array = (int**) malloc(sizeof(int *) * m);
for (int i = 0; i < m; i++)
{
array[i] = (int*)malloc(sizeof(int) * n);
}
return array;
}

void DeleteHeap(int** array, int m, int n)
{
for (int i = 0; i < m; i++)
{
free(array[i]);
array[i] = NULL;
}
free(array);
array = NULL;
}

void main()
{
int** ppArray1 = NULL;
int** ppArray2 = NULL;
int m, n;
scanf("%d, %d", &m, &n);

ppArray1 = RequestHeap1(m, n);
ppArray1[0][0] = 5;
printf("%d, %x\n", ppArray1[0][0], ppArray1);
DeleteHeap(ppArray1, m, n);

RequestHeap2(&(ppArray2), m, n);
ppArray2[0][0] = 5;
printf("%d, %x\n", ppArray2[0][0], ppArray2);
DeleteHeap(ppArray2, m, n);
}


我们使用了两种方法,
(1) int** RequestHeap1(int m, int n),在函数内部申请动态二维数组空间,并返回指针。
(2)void RequestHeap2(int*** array, int m, int n), 通过参数int *** array传递一个二维数组的首地址的指针,来完成二维数组的创建 运行结果如下: 内存分配中的堆和栈
运行成功!