1、什么是堆栈? 2、一道微软的笔试题。3、自己写的两个关于堆栈的例子? 4、如何动态申请二维数组? 一、什么是堆栈?1、内存分配一个由C/C++编译的程序占用的内存分为以下几个部分1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。 2、堆区(heap) — 一般由程序员分配释放 , 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。 3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域(BSS)。 - 程序结束后由系统释放 4、文字常量区 — 常量字符串就是放在这里的。 程序结束后由系统释放 5、程序代码区— 存放函数体的二进制代码。 一个简单的例子
[html] view plaincopy
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
根据上面的实例,我们来分析一下堆栈空间的使用情况。
(A) 是错误的,原因是函数声明中返回的是int,而实际返回的a是int*。现在让我们思考一下,如果在函数的声明修改为:int *f()。这样返回出去的指针指向的空间是否可用? 我们知道,使用new申请的动态空间是在堆上申请的,其申请和释放都是由程序员来操作的,所以在函数返回时,它不会随着函数的返回而释放。这一点与栈空间不同,在函数返回时,在该函数内部的申请的栈空间都将被系统释放。为了保证程序的安全性,该函数返回的栈空间指针是不可用的。但是实际上,我们可以使用任何一个栈空间上的指针去访问栈空间的。
(B)是错误的,原因上面提到了,int a[3] = {1, 2, 3} 是局部变量,其位于栈空间。在函数返回时,我们返回了指针a,如果在返回指针之后,为了安全着想,我们将不使用该指针。下面我将使用实例来说明不安全性。
[html] view plaincopy
这段程序的输出结果为:
4,5
1245056, 4201335
很显然,在返回指针c后,直接输出c[0]c[1],此时栈空间还么有被覆盖,所以可以正确的输出4,5。但是执行了第一个输出函数之后,可能覆盖了原来栈空间的内容,再进行输出的时候,就输出的是garbage value.因此这是不安全的。
(C) 是错误的,原因和B一样的
(D) 是错误的,原因有二,一是函数内申请的是栈空间。二是 ret是形参,所以当函数返回时,值不会发生变化。举个例子来说明:
[html] view plaincopy
3、自己写的两个关于堆栈的例子?
为了说明上述选项D的原因,我自己写了一个程序来说明情况。
[html] view plaincopy
程序运行结果如下:
在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) int** RequestHeap1(int m, int n),在函数内部申请动态二维数组空间,并返回指针。
(2)void RequestHeap2(int*** array, int m, int n), 通过参数int *** array传递一个二维数组的首地址的指针,来完成二维数组的创建
运行结果如下:
运行成功!
(责任编辑:IT) |