【问题标题】:free(): double free detected in tcache 2 in C++free():在 C++ 的 tcache 2 中检测到双重释放
【发布时间】:2020-01-22 04:07:32
【问题描述】:

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

在处理玩具 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 constructor\n";
    }

    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 empty\n";
        }

        if (str) {
            delete[] str;
        } else {
            std::cout << "str is empty\n";
        }
    }

    Foo(const Foo&) = delete;
    Foo& operator= (const Foo&) = delete;

    Foo(Foo&& rhs)
    {
        std::cout << "Move constructor is triggered\n";

        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 triggered\n";

        // 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

【问题讨论】:

  • 移动分配不应该是这样的吗?:Foo&amp; operator= (Foo&amp;&amp; rhs)
  • 是的,我修好了那部分,谢谢。
  • if (str) { delete[] str; } 在移动构造函数中没有意义。 str 在调用之前未在成员初始化(或任何其他方式)中初始化。几乎看起来有人复制/粘贴了拙劣的复制分配运算符或移动分配运算符。 val 也是如此。我希望 move-ctor 看起来像 more like this
  • 现在我明白了。感谢您的解释。

标签: c++ move-constructor construct copy-assignment move-assignment-operator


【解决方案1】:

首先,这个构造函数使用了错误的内存初始化器

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 triggered\n";

    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 triggered\n";

    std::swap( val, rhs.val );
    std::swap( str, rhs.str );
}

这个运算符

Foo& operator= (Foo& rhs)

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

main 中也是这个语句

Foo{1, "Hello World"};

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

在此声明中

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

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

【讨论】:

  • 首先感谢您的回答。我想知道:如果这些指针指针已经指向某个地方,“难道 val(nullptr),str(nullptr)”会导致内存泄漏吗?
  • 我的意思是在 C++ 中尝试一些奇怪的东西,看看会发生什么。现在我看到我在这段时间里犯了很多错误。感谢您指出。
【解决方案2】:

这可能是管理自己记忆的练习的一部分,但在生产中,我尝试永远致电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 empty\n";
        }

        if (!str.has_value()) {
            std::cout << "str is empty\n";
        }
    }

    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

【讨论】:

    【解决方案3】:

    我的回答与问题中的特定上下文无关,但与标题有关。我来了

    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 解决了问题

    【讨论】:

      猜你喜欢
      • 2022-01-08
      • 2021-12-19
      • 2022-01-05
      • 1970-01-01
      • 1970-01-01
      • 2021-05-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多