free():在 C++ 中的 tcache 2 中检测到双重释放

free(): double free detected in tcache 2 in C++

本文关键字:检测 释放 tcache 中的 C++ free      更新时间:2023-10-16

首先,我真的检查了是否已经提出了一个问题,但我找不到任何问题。错误消息不应该欺骗你我的情况有点不同,我猜或者我只是错过了一些东西。

当我处理玩具C++代码时,我遇到了一个奇怪的错误。程序输出显示存在双重释放情况,但我看不到发生此错误的位置。代码可能会有点长,对此我深表歉意。

我现在正在研究一个Linux Distribution,我正在使用g++ 9.1.0.我检查了我的代码并查找了错误的部分。

即使我修复了代码的某些部分,我的问题也没有得到解决,除非我Foo{1, "Hello World"};vec.push_back(std::move(Foo{}));发表评论,我不明白为什么。

class Foo
{
public:
Foo()
: val{nullptr}, str{nullptr}
{
std::cout << "You are in empty constructorn";
}
Foo(int the_val, const char *the_str)
: val{new int}, str{new char[std::strlen(the_str + 1)]}
{
*val = the_val;
std::cout << *val << 'n';
std::strcpy(str, the_str);
std::cout << str << 'n';
}
~Foo()
{
if (val) {
delete val;
} else {
std::cout << "val is emptyn";
}
if (str) {
delete[] str;
} else {
std::cout << "str is emptyn";
}
}
Foo(const Foo&) = delete;
Foo& operator= (const Foo&) = delete;
Foo(Foo&& rhs)
{
std::cout << "Move constructor is triggeredn";
if (val) {
delete val;
}
val = rhs.val;
rhs.val = nullptr;
if (str) {
delete[] str;
}
str = rhs.str;
rhs.str = nullptr;
}
Foo& operator= (Foo& rhs)
{
std::cout << "Move assignment is triggeredn";
// Self-assignment detection
if (&rhs == this) {
return *this;
}
if (val) {
delete val;
}
val = rhs.val;
rhs.val = nullptr;
if (str) {
delete[] str;
}
str = rhs.str;
rhs.str = nullptr;
return *this;
}
private:
int *val;
char *str;
};

int main()
{
Foo{1, "Hello World"};
std::vector<Foo> vec;
vec.push_back(std::move(Foo{}));
return 0;
}

如果我在函数 main 中的任何位置都没有评论,则输出如下。

1
Hello World
You are in empty constructor
val is empty
str is empty
You are in empty constructor
Move constructor is triggered
free(): double free detected in tcache 2
Aborted (core dumped)

如果我评论"Foo{1, "Hello World"};",输出变为

You are in empty constructor
Move constructor is triggered
val is empty
str is empty
val is empty
str is empty

最后,当我评论"vec.push_back(std::move(Foo{}((;"时,输出变为

You are in empty constructor
Move constructor is triggered
val is empty
str is empty
val is empty
str is empty

对于初学者,此构造函数使用错误的 mem 初始值设定项

Foo(int the_val, const char *the_str)
: val{new int}, str{new char[std::strlen(the_str + 1)]}
^^^^^^^^^^^

我想你的意思是

Foo(int the_val, const char *the_str)
: val{new int}, str{new char[std::strlen(the_str  ) + 1]}

此移动构造函数也无效

Foo(Foo&& rhs)
{
std::cout << "Move constructor is triggeredn";
if (val) {
delete val;
}
val = rhs.val;
rhs.val = nullptr;
if (str) {
delete[] str;
}
str = rhs.str;
rhs.str = nullptr;
}

在构造函数的主体中,数据成员valstr具有不确定的值。当构造函数的主体获得控件时,它们未初始化。

你可以这样写

Foo(Foo&& rhs) : val( nullptr ), str( nullptr )
{
std::cout << "Move constructor is triggeredn";
std::swap( val, rhs.val );
std::swap( str, rhs.str );
}

此运算符

Foo& operator= (Foo& rhs)

不是移动赋值运算符。它是一个复制赋值运算符。所以它的定义是不正确的。

还有这句话在主要

Foo{1, "Hello World"};

没有意义。对象随即创建并立即删除。

在此声明中

vec.push_back(std::move(Foo{}));

std::move是多余的Foo{}因为 已经是一个 rvalue。

我的回答与问题中的特定上下文无关,但与标题有关。 在这里我得到了

free(): double free detected in tcache 2
Aborted (core dumped)

使用 main(( { } 方法执行我的二进制文件时出现上述错误消息。 当使用内存泄漏工具检查时,除了使用该程序的类的每个字符串静态成员外,未检测到任何内容,该程序被标记为泄漏,这是不可能的。 所以注释掉了main中的所有代码。还是有问题。 仔细查看make文件后,发现问题是由于链接引起的。 对于C++文件的子集,它们被链接两次。一次通过临时库,另一次通过 make 依赖项。 我花了两天时间才弄清楚这一点。希望这个帖子可以帮助少数不够细心的人。

这是我 Makefile.am 文件的代码片段,用于说明我的问题。

lib_LTLIBRARIES = libfoo.la
libfoo_la_SOURCES=/*many not listed*/ abc.cpp xyz.cpp
LDADD = libfoo.la /*many others*/
prog_SOURCES=prog.cpp abc.cpp
prog_LDADD = $(LDADD) -lpthread /*some others*/

从prog_SOURCES中删除 abc.cpp 解决了问题

这可能是管理自己记忆的练习的一部分,但在生产中,我尽量不要打电话给newdelete

我会这样做:

class Foo {
public:
Foo() = default;
Foo(int the_val, std::string the_str)
: val{the_val}, str{std::move(the_str)}
{
std::cout << *val << 'n';
std::cout << *str << 'n';
}
~Foo() {
if (!val.has_value()) {
std::cout << "val is emptyn";
}
if (!str.has_value()) {
std::cout << "str is emptyn";
}
}
Foo(const Foo&) = delete;
Foo& operator= (const Foo&) = delete;
Foo(Foo&& rhs) = default;
private:
std::optional<int> val;
std::optional<std::string> str;
};

或者,如果您并不真正需要可选性:

class Foo1 {
public:
Foo1() = default;
Foo1(int the_val, std::string the_str)
: val{the_val}, str{std::move(the_str)}
{}
private:
int val = 0;
std::string str;
};

https://godbolt.org/z/1WPPex33h