为什么使用赋值时栈变量的析构函数没有被调用?

3

这个愚蠢的代码片段已经让我花费了两个小时,我无法弄清楚为什么第一个元素(大小为7)的析构函数没有被调用?那些由new uint16_t[7]分配的内存发生了什么?

#include <iostream>

using namespace std;

struct Node
{
    Node(uint16_t n) : p(new uint16_t[n]) {
        cout<<"Constructed with size= "<<n<<", memory addr: "<<(p)<<endl;
        for(uint16_t i=0; i<n; i++) p[i] = n;
    }

    ~Node() {
        cout<<"Destructor for p[0] = "<< *p <<" with memory addr: "<<p<<endl;
        delete[] p;
    }

    uint16_t *p;
};

int main()
{
    {
        Node nd1(7);
        {
            nd1 = Node(3);
            cout << "1st place holder" << endl;
        }
        cout << "2nd place holder" << endl;
    }

    return 0;
}

输出结果为:
Constructed with size= 7, memory addr: 0x158cc20                                                                                                                                   
Constructed with size= 3, memory addr: 0x158cc40                                                                                                                                   
Destructor for p[0] = 3 with memory addr: 0x158cc40                                                                                                                                
1st place holder                                                                                                                                                                   
2nd place holder                                                                                                                                                                   
Destructor for p[0] = 0 with memory addr: 0x158cc40                                                                                                                                
*** Error in `./a.out': double free or corruption (fasttop): 0x000000000158cc40 ***                                                                                                
Aborted (core dumped) 

6
双重释放是由于你的类违反了3/5/0规则所致:[https://dev59.com/eG855IYBdhLWcg3wvXDd]。 - drescherjm
1
内置的operator=()将执行类的成员逐一复制,这最终会复制指针的值。 - drescherjm
@drescherjm 实际上我期望的是当我使用=运算符时,首先会调用Node(7)的析构函数,然后才会进行赋值。但显然并不是这样。 - crbah
4
赋值运算符(operator=())没有调用Node(7)的析构函数。 - drescherjm
2个回答

5
我不明白为什么第一个大小为7的元素的析构函数没有被调用?
它确实被调用了,事实上正是这个析构函数导致程序崩溃。程序的行为未定义,因为析构函数删除了先前由临时对象的析构函数删除的相同指针值。在此之前,它还通过无效的指针间接引用。
新分配的uint16_t [7]的内存发生了什么情况?
当您对该对象进行赋值时,指向该内存的指针丢失了。这被称为内存泄漏。

3
这段代码:nd1 = Node(3);并不像你期望的那样工作。
没有替换nd1Node(3)并删除旧的nd1
它所做的是创建一个新的临时节点实例Node(3),并将每个成员的值复制到nd1中。因此,临时节点和nd1都包含指向同一地址的指针。在这一点上,由于没有指针引用它,因此您泄漏了程序开始时nd1分配的内存,但您没有删除它。
当临时对象消失时,nd1指向已释放的内存。当nd1在第二个}处运行其析构函数时,它再次删除相同的指针,因此出现错误。
要解决这个问题,您需要实现所谓的五个规则或零个规则之一。
最简单的是零规则。只需使用unique_ptr即可按预期进行销毁:
struct Node
{
    Node(uint16_t n) : p(std::make_unique<uint16_t[]>(n)) {
        cout<<"Constructed with size= "<<n<<", memory addr: "<<(p)<<endl;
        for(uint16_t i=0; i<n; i++) p[i] = n;
    }

    std::unique_ptr<uint16_t[]> p;
};

int main()
{
    {
        Node nd1(7);
        {
            nd1 = Node(3); // assignement destroys the old buffer
            cout << "1st place holder" << endl;
        }
        cout << "2nd place holder" << endl;
    }

    return 0;
}

1
这段代码:nd1 = Node(3); 并没有达到你的预期效果。--> 没错,我原本期望的是一系列操作,首先需要清除现有值,然后进行新的赋值。 - crbah
1
你忘了从析构函数中删除现在多余的 delete[] p;。此外,对于 C++14 或更高版本,更安全(对于可能需要初始化其他状态的更复杂代码)的做法是将初始化器从 p(new uint16_t[n]) 更改为 p(std::make_unique<uint16_t[]>(n)),因为它保证执行顺序不会导致未排序代码中的异常在分配之后但在 unique_ptr 初始化之前发生,从而导致泄漏。 - ShadowRanger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接