记录一个function作用域引发的坑,不了解C++的可以直接看文章后面的多线程内存分析,应该对解bug有所借鉴。
C++中std::function可以对任何代码块进行封装调用,比如普通函数、lambda函数等等,但是对对于类内定义的function在使用类内元素时要注意生命周期的管理,看一下下面的例子:
定义一个func类,并在局部代码块里实例化一个对象,将该对象中的function传出给全局变量funcm。当执行完代码块的时候,func对象已经析构了,也就是其申请的内存已经释放了,那么继续执行function调用会是什么情况呢?运行一下看一下:
可以看到Func类虽然结束生命周期释放了自己申请的内存,main函数(主线程)中funcm仍然正确执行了。事实上,func析构确实将自己申请的内存释放了,但是因为是单线程且后续操作未执行malloc申请内存操作(就算申请内存不执行写操作,脏数据仍然在内存中),因此function执行并未出现异常。
那么在局部代码执行完毕后申请内存并写入数据试试:
执行结果如下:
到这里就明白了,同进程内两次申请同样大小的内存肯定会申请到相同虚拟地址,因为对于执行程序的内核而言其内存分配算法是固定的(不管是伙伴算法还是FirstFit等),具体原因大家可以琢磨一下。因此虽然局部函数里面申请的内存释放了,但是后续申请到相同的地址并进行写操作,就会影响function的调用。
多线程访问私有栈
到这里可以延申一下,多余多线程场景,如果某线程读取另一线程的栈数据(非堆数据),是否能正确读取呢?实验一下:
结果如下:
但是这里要注意不能让主线程提前回收子线程资源,也就是需要让子线程睡眠一下,否则会触发段错误。另外,局部变量不一定会保存在栈中,对于调用较多的变量可能保存在寄存器中。
再看一下,子线程申请内存并写入数据,之后释放内存,主线程等待子线程操作完毕后申请同样大小的内存,会发生什么情况呢:
运行结果如下:
可以看到主线程中存有子线程的脏数据。原因是因为多线程是共享TLB表的,对于多进程,因为不同进程有自己的页表,也就是相同虚拟地址会映射到不同物理内存地址,就不会出现这种情况。