析构函数中未释放资源¶
ID: cpp/resource-not-released-in-destructor
Kind: problem
Security severity:
Severity: warning
Precision: high
Tags:
- efficiency
- readability
- external/cwe/cwe-404
- external/jsf
Query suites:
- cpp-security-and-quality.qls
此规则查找由类分配但未在该类的析构函数中释放的资源。分配资源包括
使用
malloc
分配内存使用
new
创建对象打开文件
打开网络套接字资源管理可能是一项复杂的任务,因此一个标准最佳实践是“资源获取即初始化”(RAII)模式。在 RAII 类中,构造函数分配所有必需的资源,析构函数释放所有资源。这保证了只需删除类的实例就足以释放资源,并得益于 C++ 的自动对象生命周期管理。只要 RAII 类的生命周期得到适当管理,设计良好的 RAII 类就不可能成为资源泄漏的来源。
如果使用
new
分配,则应由创建它的客户端使用delete
释放如果其生命周期是词汇的(它仅在一个函数中使用),那么将其声明为该函数的局部变量(而不是指针)就足够了,C++ 运行时将确保在退出时释放它。有两个可能的消息
“资源 *x* 由类 *C* 获取但未在析构函数中释放。它从 *f* 中释放,因此可能需要在析构函数中调用此函数”。这表明资源(*x*)正在被释放,只是不在类的析构函数中。通常,它是在名为
close
、free
或类似名称的函数中释放的。这并不总是表示资源泄漏,但它表明该类使用起来不必要地复杂,因为它不符合 RAII 模式。“资源 *x* 由类 *C* 获取,但未在该类的任何地方释放”。这表明该类正在分配资源,但没有负责释放它们。这非常容易出错:即使类需要显式关闭运算符,它也应该管理它分配的任何资源,而不是强迫客户端管理它们。在最坏的情况下,如果客户端代码未释放资源,这可能导致资源泄漏。
建议¶
如果资源根本没有被释放,请确保该类释放了资源,通常是通过将释放操作添加到该类的析构函数中。此更改需要仔细验证:客户端代码可能依赖于资源比分配它的类生存时间更长,并且如果需要,必须审查和更新。
在另一种情况下,例如具有显式 close
函数的类,目标是将该类迁移到简单的 RAII 模式。这可以通过几个步骤来实现
首先,确保
close
函数(或其等效项)可以安全地调用两次,仅在资源尚未释放的情况下才释放资源。接下来,从析构函数中调用
close
函数。这不需要更改客户端代码,因为它可以安全地调用两次。迁移客户端代码以删除对
close
函数的直接使用,并借此机会检查对象本身是否被正确删除。最后,如果可能,也将初始化代码迁移到类的构造函数中,使其完全遵循 RAII 模式。
示例¶
// This class opens a file but never closes it. Even its clients
// cannot close the file
class ResourceLeak {
private:
int sockfd;
FILE* file;
public:
C() {
sockfd = socket(AF_INET, SOCK_STREAM, 0);
}
void f() {
file = fopen("foo.txt", "r");
...
}
};
// This class relies on its client to release any stream it
// allocates. Note that this means the client must have
// intimate knowledge of the implementation of the class to
// decide whether it is safe to release the stream.
class StreamPool {
private:
Stream *instance;
public:
Stream *createStream(char *name) {
if (!instance)
instance = new Stream(name);
return instance;
}
}
// This class handles its resources, but does not do that in
// the constructor/destructor. It can be rewritten easily to
// be safer to use.
class StreamHandler {
private:
char *_name;
Stream *stream;
public:
C(char *name) {
_name = strdup(name):
}
void open() {
stream = new Stream();
}
void close() {
delete stream;
}
~StreamHandler() {
free(_name);
// stream should be deleted here, not in close()
}
}