1.函数介绍
对于同样功能的递归函数,分别用传指针和传值的方式进行参数传递,观察这两种传参的方式对他们递归深度的影响。在用不同大小的字符串、不同大小的栈空间分别进行测试。函数设置为死循环,让函数无穷的递归,观察函数在栈溢出崩溃时能递归的最大次数。下面的实验中,分别让函数传递一个指针参数、一个引用参数、一个string参数。
2.实验结果
限定栈空间下不同的参数传递方式能递归的深度(单位:递归次数)
字符串长度 | 栈空间8192KB | 栈空间1024KB | ||||
传指针方式 | 传引用方式 | 传值方式 | 传指针方式 | 传引用方式 | 传值方式 | |
2000 | 327426 | 327351 | 218296 | 98050 | 98045 | 65268 |
20000 | 326689 | 326712 | 217951 | 97351 | 97404 | 64848 |
200000 | 321068 | 321214 | 214067 | 91703 | 91826 | 61208 |
3.结果分析
从表中看出:
(1)传指针和传引用基本没有差别。
(2)用传指针或引用的方式比用传值的方式进行递归时,递归次数要多。传指针和传引用能递归的次数大概是传值方式的1.5倍。
(3)无论是那种传参方式,字符串变大之后,递归次数都会略微减少,但是这种变化不是特别明显。
4.更深入的分析:
(1)传指针方式:
代码:
//递归函数1:传指针
void recursion_1(char* x){
count++;
cout << "当前递归层次:" << count <<endl;
cout << "指针x的大小:" << sizeof(x) << endl;
cout << "指针x的地址: " << &x << endl;
cout << endl;
recursion_1(x);
}
调用结果:
从上图中可以看出,程序在递归到第327300层的时候栈溢出崩溃。从函数的调用方式知道,每次发生函数调用时,是函数的参数首先入栈,本函数只有一个参数。因此这里的参数(即指针x)是处于栈底的元素,因此我们可以计算两个层次指针地址的差值来推测函数系统为函数分配了多大的栈空间。计算得知,相邻两个函数的指针x的地址差值为20,当然这是十六进制的值,转换为十进制以后是32个字节。反过来,既然系统为每层函数都分配了32字节的栈空间,那么递归到327300层函数一共需要多少栈空间呢?计算结果为9.98MB。可是明明使用ulimit -s命令查看是8192KB即8MB的空间啊。百思不解,查阅相关资料,在一个论坛上有人说,ulimit命令查看到的值是系统保证最低提供这么多空间,但不是一定是这么多,也就是说8192不是个准确值,系统只保证给你的栈不小于这个值。
<pre name="code" class="cpp">//递归函数1:传指针
void recursion_1(char* x, char *y, char *z, char *p, char *t){
count++;
cout << "当前递归层次:" << count <<endl;
cout << "指针x的大小:" << sizeof(x) << endl;
cout << "指针x的地址: " << &x << endl;
cout << "指针y的地址: " << &y << endl;
cout << "指针z的地址: " << &z << endl;
cout << "指针p的地址: " << &p << endl;
cout << "指针t的地址: " << &t << endl;
cout << endl;
recursion_1(x,y,z,p,t);
}
继续增加指针参数的数量,当指针参数数量为1~4个时,发现每次递归占用的栈空间大小仍然为32B。当指针参数增加到5个时,函数递归时占用的空间增大到48B,从上图中可以看出来,递归层次有所降低,经过换算,递归218013次所需的栈空间仍然为为9.98MB。从这里可以看出栈在分配时貌似是以16B为单位对齐的。另外从这几个参数的地址可以看出,函数的参数是从右向左压栈的。指针t最先入栈,在栈底。
//递归函数1:传指针
void recursion_1(){
count++;
cout << "当前递归层次:" << count <<endl;
cout << endl;
recursion_1();
}
进一步探究,当函数的参数为0个的时候,发现递归层数和1个指针参数时层数差不多,说明函数仍然分配了32B的栈空间。难道函数最低要分配这么多栈空间吗?
(2)传引用方式
传引用方式的行为和传指针类似。
(3)传值方式
//递归函数3:传值
void recursion_3(string x){
count++;
cout << "当前递归层次:" << count <<endl;
cout << "字符串x的大小:" << sizeof(x) << endl;
cout << "字符串x的地址:" << &x << endl;
cout << endl;
recursion_3(x);
}
传递一个string参数时,通过参数的地址计算每个函数分配的栈空间为48B。在这里需要注意的是字符串的大小为4个字节,原因是string为对象类型,他所占用的内存空间在堆中分配,在栈上面只保存它的一个指针。
//递归函数3:传值
void recursion_3(string x, string y){
count++;
cout << "当前递归层次:" << count <<endl;
cout << "字符串x的大小:" << sizeof(x) << endl;
cout << "字符串x的地址:" << &x << endl;
cout << "字符串y的大小:" << sizeof(y) << endl;
cout << "字符串y的地址:" << &y << endl;
cout << endl;
recursion_3(x, y);
}
从图中可以看出,每个函数所占用的栈空间仍然为48字节。观察字符串x和y的地址,发现x的地址要比y的地址大,说明x比y先入栈。难道函数的参数又从左向右进行压栈了?
//递归函数3:传值
void recursion_3(string x, string y, string z, string p){
count++;
cout << "当前递归层次:" << count <<endl;
cout << "字符串x的大小:" << sizeof(x) << endl;
cout << "字符串x的地址:" << &x << endl;
cout << "字符串y的大小:" << sizeof(y) << endl;
cout << "字符串y的地址:" << &y << endl;
cout << endl;
recursion_3(x, y, z, p);
}
继续做测试,当string参数数量增加到4个时,仍然按照48B分配栈空间,进一步说明了栈空间的分配按照16个字节对齐。
//递归函数3:传值
void recursion_3(string x, string y, string z, string p, string q){
count++;
cout << "当前递归层次:" << count <<endl;
cout << "字符串x的大小:" << sizeof(x) << endl;
cout << "字符串x的地址:" << &x << endl;
cout << "字符串y的大小:" << sizeof(y) << endl;
cout << "字符串y的地址:" << &y << endl;
cout << endl;
recursion_3(x, y, z, p, q);
}
当string参数数量增加到5个的时候,每个函数分配的栈空间为80字节。按照规律的话,每个函数应该分配64字节的栈空间才对,可是这里为什么跳过了64字节直接到了80字节?没搞明白。按照每个函数80字节计算,递归130863次的话共计需要9.98MB栈空间。同前面的分析相符,说明ulimit真的“不靠谱”,他告诉我们只有8M的栈,实际上给了我们将近10M。
5.分析总结
(1)函数参数为0个时,函数占用32字节的栈空间。
(2)当函数的参数为指针时,每个指针占用4个字节。但是当指针参数个数为1~4个时,每个函数仍然分配32字节的栈空间。当指针参数增加到5个时,每个函数分配48字节的空间,增长了16个字节,说明栈在分配时是以16字节为单位对齐的。
(3)当函数的参数为指针时,参数是从右到左压栈的。
(4)当函数的参数为引用时,占用空间与指针一致。
(5)当函数的参数为string时,1~4个string参数函数会分配48字节的栈空间,5个string作参数时,分配80个字节。没错,就是跳过了64字节,直接分配了80个字节,增长了32字节。
(6)当函数的参数为string时,参数是从左到右压栈的。
(7)当函数的参数为string时,在栈上实际上只保留string的一个指针,string真正的内容保存在堆中。所以即使string字符串很长,在栈上面也体现不出来。用sizeof()运算符查看,可以看到string的大小为4。注意:sizeof()只能查看在栈上面的空间大小,该运算符对堆无效
6.源代码
#include <iostream>
#include <string>
#include <cstdlib>
#include <sys/time.h>
using namespace std;
const int LEN = 2000;
long count = 0;
//产生随机字符串
char* rand_string(char* str, int len){
int i = 0;
srand((unsigned)time(NULL));
for(i = 0; i < len; i++){
str[i] = rand()%26 + 'a';
}
str[i] = '\0';
return str;
}
//递归函数1:传指针
void recursion_1(){
count++;
cout << "当前递归层次:" << count <<endl;
cout << endl;
recursion_1();
}
//递归函数2:传引用
void recursion_2(string &x, string &y, string &z, string &t, string &p){
count++;
cout << "当前递归层次:" << count <<endl;
cout << "引用变量x的大小:" << sizeof(x) << endl;
cout << "引用变量x的地址:" << &x << endl;
cout << endl;
recursion_2(x,y,z,t,p);
}
//递归函数3:传值
void recursion_3(string x, string y, string z, string p){
count++;
cout << "当前递归层次:" << count <<endl;
cout << "字符串x的大小:" << sizeof(x) << endl;
cout << "字符串x的地址:" << &x << endl;
cout << "字符串y的大小:" << sizeof(y) << endl;
cout << "字符串y的地址:" << &y << endl;
cout << endl;
recursion_3(x, y, z, p);
}
int main(){
char str_1[LEN+1];
rand_string(str_1, LEN);
string x(str_1);
sleep(1);
char str_2[LEN+1];
rand_string(str_2, LEN);
string y(str_2);
char str_3[LEN+1];
char str_4[LEN+1];
char str_5[LEN+1];
int i = 0;
//recursion_1();
recursion_2(x,y,x,x,x);
//recursion_3(x, y, x, x);
}