原始问题
起源于《APUE》的习题7.10
int f1(int val) {
int num = 0;
int* ptr = #
if (val == 0) {
int val = 5;
ptr = &val;
}
return (*ptr + 1);
}
问题是上述代码是否正确?可以看到,如果调用f1(0)
,那么就会进入if块,定义局部变量val,在离开if块后,val的内存会被自动回收。因为局部变量是定义在栈上的。
参考Automatic_variable
In computer programming, an automatic variable is a local variable which is allocated and deallocated automatically when program flow enters and leaves the variable's scope.
然而实际的测试结果,f1(0)
能够成功地输出结果6。
提出疑问,会不会是要过段时间才回收呢?在返回前加上一句sleep(10)
后仍然运行正确。使用volatile
修饰也没问题,那么不是从寄存器中读取旧的值。
局部变量是C++对象的情况下是否析构
接下来,我使用了C++的类来包装int
struct Integer {
Integer(int i) : _i(i) { printf("Int(%d)\n", i); }
~Integer() { printf("~Int(%d)\n", _i); }
int _i;
};
int f1(int val) {
Integer num = 0;
Integer* ptr = #
if (val == 0) {
Integer val = 5;
ptr = &val;
}
return ptr->_i + 1;
}
调用f1(0)
的结果如下
Int(0)
Int(5)
~Int(5)
~Int(0)
6
析构函数调用了,但是仍然输出了正确结果。C++对象的构造析构均是两段式,申请内存+构造对象 & 析构对象+释放内存,但这里看似只执行了第一步。
另一个案例
搜索原因的过程中找到的帖子 C local variable reused
#include<stdio.h>
void t();
int main(void){
t();
t();
t();
return 0;
}
void t(){
int i;
i++;
printf("%d\n",i);
}
输出结果依次是1,2,3,这里是未初始化局部变量i。产生这种结果的原因是,函数栈帧每次被回收时,并没有实际清除所在的地址,导致下次调用该函数,使用的还是原来的栈帧地址。
但是这份代码的结果是未定义的行为,通俗点讲就是不能依赖这种小聪明,该用static
就用static
。或者为了避免这种未定义行为,必须对局部变量初始化int i = 0;
,C99/C++11标准不禁未禁止局部变量未初始化就使用的行为,还会自动初始化未初始化的int(为0)。PS: 对于函数体外的变量(即BSS:未初始化数据段)会自动初始化为0。
检测和防止
一种检测方法是编译时加上-Wall
选项会提示使用未初始化的变量。
$ g++ test.cc -std=c++11 -Wall
test.cc: In function ‘int main()’:
test.cc:23:32: warning: ‘i’ is used uninitialized in this function [-Wuninitialized]
printf("%d %d\n", i, f1(0));
再回过头看之前那个使用int
包装类Integer
的版本,在C++中由于对象每次构造都会调用构造函数来初始化各成员变量,所以不存在重用旧值的问题。
但是问题是在访问这样的本应回收的内存时,编译器不会报错。这点也是C/C++不同于大多数高级语言的地方,这种陷阱需要十分小心。