解决由全局静态变量引起的内存问题

Working around memory issues caused by global static variables

本文关键字:内存 问题 变量 全局 静态 解决      更新时间:2023-10-16

警告:出现这个问题是因为我必须处理一大堆糟糕的代码,没有适当的文档,由其他人在 6 年前作为研究项目编写。显然,更好的解决方案是首先不要通过适当的设计来引起这些问题......

也就是说,问题是:摆脱这种情况的最佳方法是什么:

  1. 类在堆上分配内存,并在析构函数中释放它。
  2. 在某处,类的实例在全局范围内声明。
  3. 存在初始化此实例的函数。
  4. 该函数的返回值用于初始化静态变量。
  5. 全局范围的变量在静态范围之外使用。

最小工作示例:

文件 "myclass.h":

#ifndef MYCLASS_H
#define MYCLASS_H
#include<vector>
using namespace std;
class myclass{
vector<int> *onTheHeap;
public:
myclass(int len=0){
onTheHeap = new vector<int>(len);
}
~myclass(){
delete onTheHeap;
}
};
#endif

文件"static_loader.cpp">

#include "class.h"
myclass existsForever;
int cause_static_global_creation(){
existsForever = myclass(5);
}
static int bootstrap = cause_static_global_creation();

并提交"main.cpp":

#include "class.h"
extern myclass existsForever;
int main(){
return 0;
}

构建方式:

g++ -g -c static_loader.cpp
g++ -g main.cpp static_loader.o

并运行为:

valgrind --leak-check=full ./a.out

结果:当变量的析构函数在 main 下方的出口处理程序中调用时,该变量被释放,但在 main下面的 static_initialization_and_destruction_0 函数中也被释放 static_loader!

有没有办法确保这些变量释放一次,而不涉及大量重构代码?在我必须使用的库中,有几十个这种模式的实例......

编辑:

添加函数:

void operator=(myclass other){
delete this->onTheHeap;
this->onTheHeap = other.onTheHeap;
}

myclass(const myclass& other){
this->onTheHeap = new vector<int>(*(other.onTheHeap));
}

不会更改行为。

第二次编辑:

myclass& operator=(const myclass& other){
delete this->onTheHeap;
this->onTheHeap = new vector<int>(*(other.onTheHeap));
return *this;
}

解决所有问题。无论如何,我的库都有这样的来源的内存泄漏,但我不再确定如何重现它。至少不是这个,也感谢关于重构等的建议!

你的假设被打破了。myclass existsForever;不是由cause_static_global_creation初始化,而是由myclass::myclass初始化。相反,cause_static_global_creation为已初始化的对象赋值。

由于该类违反了三法则,因此作业导致问题也就不足为奇了。

我认为这些评论/策略涵盖了您的情况:

  1. 如果您拥有class.h,则只需将vector<int>*替换为vector即可。 编译器将负责堆栈内存管理,您将避免堆泄漏。
  2. 请注意,全局静态将在程序执行期间消耗堆栈空间,因此逐字节这与动态内存泄漏一样"糟糕"。
  3. 您的双重删除可能是由对指针转义的引用引起的,正如 Benji 通过默认复制 ctor 指出的那样。 你可以用共享指针(不需要调用删除)替换它,但堆栈更好。 考虑禁用复制构造函数或编写一个深层复制而不是浅拷贝的复制 ctor,正如 Benji 进一步指出的那样,如果您禁用赋值和复制构造并且它没有编译,您已经发现了(其中一个)您的问题。
  4. 全局静态在内存使用中应该不是问题,除非它们是一个集合并且无限期地扩展,而不会在使用后删除它们的垃圾。 如果内部的向量没有被清理而无限扩展,那么这些向量在程序执行方面消耗恒定量的内存。 确保这些具有更紧密的生命周期将导致更好的因子代码,但如果您担心内存,则会被视为过早优化。

在您的示例中构造了两次myclass

首先,通过语句myclass existsForever;len=0

稍后,使用len=5cause_static_global_creation中构造一个临时实例,并使用默认赋值运算符将其分配给existsForever。此时,existsForever和临时指针将共享相同的onTheHeap值。临时在cause_static_global_creation中立即被销毁,释放了矢量的内存。当全局实例被销毁时,相同的内存会在程序端再次释放。

我有一些建议如何快速解决这个问题。

1.定义构造函数如下

myclass(int len=0)
{
if ( len > 0 )
onTheHeap = new vector<int>(len);
else
onTheHeap = NULL;
}

2.使用智能指针而不是裸指针。

std::shared_ptr<vector <int>> onTheHeap;

3.不要在堆上构造向量,而是使用实例成员。

更长的方法是正确实现赋值运算符和复制构造函数。