如何让共享库的全局对象在exit()函数调用后存活?
核心点:堆对象不会被exit自动销毁,但全局指针/引用会提前失效
首先明确:new Object创建的堆对象,exit()不会自动回收它——C++标准里,exit只会销毁静态/全局存储期的对象,堆内存得你主动调用delete才会释放。所以堆上的Object实例本身,只要你不主动销毁,会活到进程结束(最后由操作系统回收内存)。
但你写的pObj和rObj是全局变量,属于静态存储期,会在exit执行的「销毁全局对象」阶段被销毁(和你原来的全局Object销毁时机一模一样)。这就会导致问题:当atexit回调触发时,pObj这个指针变量已经不存在了——此时你要是在回调里直接用pObj访问堆对象,属于访问已销毁的内存,是未定义行为,大概率会引发崩溃。
可行的解决办法
1. 用函数内的静态指针做懒加载(最稳妥)
别直接定义全局指针,改成在库函数里用局部静态指针:
Object& get_lib_global_obj() { static Object* pObj = new Object; return *pObj; }
函数里的静态变量,销毁时机是进程退出的最后阶段——也就是所有atexit回调执行完之后才会销毁。所以不管是main运行时,还是atexit回调里调用库函数,get_lib_global_obj()都能正常返回堆对象的引用,完全避开销毁顺序的坑。
2. 避免依赖全局变量存储对象地址
如果一定要提前创建对象,那得确保在atexit回调里能拿到堆对象的有效地址,不能用全局变量存。比如可以在库初始化时,把堆对象地址存在静态局部变量里(本质还是回到第一种方式),或者用其他不会被提前销毁的存储介质(比如线程本地存储,但没必要)。
对Object &rObj = *(new Object);的补充
这种写法和指针版本本质一致:rObj作为全局引用,会在exit时被销毁,但堆对象还活着。但回调里直接用rObj的话,同样会访问已销毁的引用变量,触发未定义行为,所以这个写法解决不了问题。
总结
直接用全局指针/引用指向堆对象的思路走不通,因为这些指针/引用本身会在atexit回调前失效。最靠谱的方案是用函数内的静态指针实现懒加载,让指针的销毁时机晚于atexit回调,确保堆对象能被安全访问。
内容的提问来源于stack exchange,提问作者user77777777

