我们都知道,在C++中一个函数中定义的局部变量后,在程序执行完该函数的时候该内存上的数据就被自动释放了,但今天在复习数据结构链表知识与编写代码实现的的时候我发现了在使用局部变量的过程中,定义类变量需要注意的问题。
首先,我们查看一下下面两段代码,该类实现了链表的结点和尾部插入操作(实际上本尾部插入操作由于局部变量的生命周期结束后会被释放,所以是不能写的,但为了表示c++中局部变量中的类变量和基本变量的差别,特此进行测试)。
//链表类
class Test {
public:
int data;
Test* next;
Test() {
data = 0;
next = 0;
}
~Test() {
delete next;
}
void insert(int e);
};
//链表的尾部插入操作
void Test::insert(int e) {
Test s;
s.data = e;
next = &s;
}
在下面主函数中我创建一个空链表然后插入一个结点,并把结点信息打印出来。
int main()
{
Test t;
t.insert(3);
std::cout << "第一次显示插入的值:"<<t.next->data<<std::endl;//行1
std::cout << "第二次显示插入的值:"<<t.next->data<<std::endl;//行2
return 0;
}
打印结果:
第一次显示插入的值:3
第二次显示插入的值:4201761
发现问题了么?按照我们之前所学过的理论,第一次和第二次打印的结果均应该为随机值,这是因为c++的局部变量机制,在定义一个局部变量以后,当该局部变量完成它的生命周期以后,原本应该被释放,在底层中的表现为:当我们在函数中定义一个局部的类变量时,首先会执行入栈操作,而栈中的类变量退出其作用域时,会自动执行其析构函数,本不该在主函数打印3,为了更加清楚的了解到底是为什么发生了这种情况我在vs中利用debug功能查看前后到底发生了什么。
如下图所示,在执行insert函数时,局部变量在内存中的信息是有效的:
而函数运行完时程序自动跳转到析构函数执行,此时注意了,如下图所示此时对象t所指向的结点的data,以及next的信息依然没有改变!也就是说在执行即使在执行析构函数完了以后这段内存依旧可以通过指针被有效访问。
而在执行完行1,也就是: std::cout << "第一次显示插入的值:"<<t.next->data<<std::endl;后,内存中的情况变成下面这种:
起初,我的想法是会不会是释放内存需要一定时间,从而导致了在第一次打印是显示确定数值,而第二次打印显示随机数值,但是当我使用延时函数测试后发现并不是这个原因。
之后我联想到cout的机制是涉及到内存的缓冲区,所以我想到会不会是对缓冲区的操作刷新最后会使得局部类变量所占内存中的数据被改变,最终导致了第二次打印为随机值。为了验证我的猜想,我在第一打印操作前增加了一个cin 语句:
std::cin >> x;
std::cout << "第一次显示插入的值:" << t.next->data << std::endl;
std::cout << "第二次显示插入的值:" << t.next->data << std::endl;
打印结果为:
第一次显示插入的值:262866848
第二次显示插入的值:11345185
验证成功。
最终得到结论,与局部基本变量不同,局部的类变量在其生命周期结束后,该地址所存放的信息我们依旧通过指针访问,但是当执行关于缓冲区的操作(如输入输入)后,该地址的数据会被刷新,之后再访问到的就是随机数据,也就是说利用指针访问局部类变量虽然在某些情况下是可行的但是始终是不安全的。