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语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时, 会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的 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;
- }
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);
- <span style="white-space:pre"> </span>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);
- <span style="white-space:pre"> </span>int *array1 = NULL;
- <span style="white-space:pre"> </span>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传递一个二维数组的首地址的指针,来完成二维数组的创建
运行结果如下:
运行成功!