调用函数的过程

栈空间是从高地址向低地址增长的。所以压栈即表示栈顶指针变小,而出栈则相反。

函数调用者维护了一个栈空间(stack),拥有栈底指针ebp和栈顶指针esp。

调用函数时,栈变化过程简单描述如下:

1)先将函数返回地址ret压栈,即函数执行完毕后将从哪里继续执行下去

2)将ebp压栈

3)将esp赋值给ebp

4)将函数局部变量压栈

函数执行完毕后,栈变化过程如下:

1)函数局部变量出栈

2)ebp出栈,恢复ebp的值

3)函数返回地址出栈

从以上的变化过程可以看到,函数调用者通过操作ebp和esp的值变化来维护栈的变化。

函数返回局部变量

一般的来说,函数是可以返回局部变量的。 局部变量的作用域只在函数内部,在函数返回后,局部变量的内存已经释放了。因此,如果函数返回的是局部变量的值,不涉及地址,程序不会出错。

可以有两种情况:返回局部自动变量和局部静态变量

局部自动变量

1
2
3
4
5
int func()
{
int temp = 0;   // 返回局部自动变量的值
return temp;
}

局部变量temp存储在栈中,函数返回时会自动复制一份temp的copy给调用者,没有问题

1
2
3
4
5
6
vector func()
{
vector v;
v.push_back(0);
return v;
}

返回的是v的值拷贝,没有问题。

1
2
3
4
5
6
Person func()
{
Person p1;
p1.name = "test";
return p1;
}

返回的也是值拷贝,会调用Person类的拷贝构造函数,没有问题。

静态局部变量

1
2
3
4
5
int func()
{
static int a = 1;   // 返回局部静态变量的值
return a;
}

局部变量a存储在静态(全局)存储区中,从初始化后一直有效直到程序结束,仅分配一次内存,并且函数返回后,变量不会销毁,没有问题。

函数返回指针

函数返回指针其实就是返回指针指向的地址,只要地址不会失效,那么返回的指针就一直有效。

指针指向的是局部变量

可以有两种情况:返回局部自动变量和局部静态变量

局部自动变量

虽然函数执行完毕后栈“销毁”了,但在重入之前,存储在栈中的局部变量仍然存在!这个时候,通过指针来访问该位置仍然可以获得正确的值!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
int* test(int b)
{
int a=0;//局部变量
a=b; //形参复制给a
int * p=&a ; //定义一个int* 的指针,把变量a的地址给p
return p; //返回p。这个p是局部变量的指针
}
void test2()
{
int a[10];
for (int i = 0; i < 10; i++)
    a[i] = i;
}
int *result=test(5);
printf("%d\n", *result); //此时是5
test2();
printf("%d\n", *result); //此时是一个错误的值

静态局部变量

1
2
3
4
5
int* func()
{
static int temp = 1;
return &temp;
}

局部变量temp存储在静态存储区,返回指向静态存储区变量的指针是可行的。

字符串

1
2
3
4
5
char* func()
{
char *p = "test";
return p;   // 返回指向常量字符串的指针
}

对于字符串的特殊情况,由于字符串test存储在常量存储区(不是静态存储区),因此函数返回一个指向常量的字符串指针是可行的。

1
2
3
4
5
char* func()
{
char str[] = "test";
return str; // 返回局部字符串的指针
}

这种情况下,str被初始化为字符串局部变量,因此函数返回一个已销毁的局部变量是不可行的。

指向局部自动变量的方法

利用new或malloc使变量在堆中存放。这种情况下,函数返回一个指向堆内存的指针,由于堆存储区由程序员手动管理,因此这种做法是可行的,但是要防止出现内存泄露,函数调用完后需要手动释放内存。这里的sizeof作用于指针返回的是指针类型的长度1byte,而如果作用于数组返回的则是数组的长度。

1
2
3
4
5
6
char* func()
{
char *str = (char *)malloc(sizeof(char) * BUFFER_SIZE);
strcpy(str, "test");
return str;
}

指针指向的是参数或NULL

指针指向其中一个参数或NULL,是可以的。

ListNode *mergeTwoLists(ListNode *l1, ListNode *l2) { if (l1 == NULL) return l2; if (l2 == NULL) return l1; ListNode *ret = NULL; if (l1->val < l2->val) { ret = l1; ret->next = mergeTwoLists(l1->next, l2); } else { ret = l2; ret->next = mergeTwoLists(l1, l2->next); } return ret; }

函数返回引用

引用和指针基本一致,唯一的不同是引用不能返回NULL