You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

移动赋值运算符引发段错误,求拷贝/移动构造与赋值无泄漏方案

问题分析与解决方案

代码中的核心错误

  • 空悬指针访问触发段错误
    main函数里,obj被std::move传递给moj后,obj的p已被置为nullptr。后续又将std::move(obj)赋值给loj,此时loj的p会被设置为nullptr,调用loj.printData()时直接访问nullptr的下标,触发Segmentation fault。
  • 析构函数的未定义行为
    当对象的p为nullptr(比如被移动后的源对象),析构函数中循环执行delete [] p[i]会尝试访问nullptr的成员,这属于未定义行为,会被valgrind检测为错误,甚至导致崩溃。

修复后的完整代码

#include <iostream>
using namespace std;

class MY{
    int **p;
    // 提取内存分配/释放的辅助函数,避免重复代码
    void allocateMemory() {
        p = new int*[4];
        for(int i=0;i<4;i++)
            p[i] = new int[5];
    }

    void freeMemory() {
        if (p == nullptr) return;
        for(int i=0;i<4;i++)
            delete [] p[i];
        delete [] p;
        p = nullptr; // 释放后置空,避免野指针
    }
public:
    MY(){
        cout << "Default Constructor" << endl;
        allocateMemory();
    }

    // Copy Constructor
    MY(const MY& obj){
        cout << "Copy Constructor" << endl;
        allocateMemory();
        for(int i=0;i<4;i++){
            for(int j=0;j<5;j++)
                p[i][j] = obj.p[i][j];
        }
    }

    // Copy Assignment Operator
    MY& operator = (const MY & obj){
        cout << "Copy Assignment Operator" << endl;
        if(this == &obj)
            return *this;

        freeMemory();
        allocateMemory();

        for(int i=0;i<4;i++){
            for(int j=0;j<5;j++)
                p[i][j] = obj.p[i][j];
        }
        return *this;
    }

    // Move Constructor
    MY(MY && obj) noexcept { // 添加noexcept,符合标准库移动语义要求
        cout << "Move Constructor" << endl;
        p = obj.p;
        obj.p = nullptr;
    }

    // Move Assignment Operator
    MY& operator = (MY && obj) noexcept { // 添加noexcept
        cout << "Move Assignment Operator" << endl;
        if(this == &obj)
            return *this;

        freeMemory();
        p = obj.p;
        obj.p = nullptr;

        return *this;
    }

    ~MY(){
        cout << "Destructor " << endl;
        freeMemory();
    }

    void setData(){
        if (p == nullptr) return; // 避免空指针访问
        for(int i=0;i<4;i++){
            for(int j=0;j<5;j++)
                p[i][j] = i*j;
        }
    }

    void printData(){
        if (p == nullptr) {
            cout << "Object has no valid data (moved from)" << endl;
            return;
        }
        for(int i=0;i<4;i++){
            for(int j=0;j<5;j++)
                cout << p[i][j] << " ";
            cout << endl;
        }
    }
};

int main()
{
    MY obj;
    obj.setData();
    obj.printData();

    MY moj(std::move(obj));
    moj.printData();

    // 注意:被移动后的obj处于有效但空的状态,不要再使用其数据成员
    MY loj;
    // 改为移动仍持有有效数据的moj
    loj = std::move(moj); 
    loj.printData();

    // 此时moj也处于空状态,调用printData会提示无有效数据
    moj.printData();
}

关键修复点说明

  • 提取辅助函数:将内存分配(allocateMemory)和释放(freeMemory)逻辑抽离,避免代码重复,同时统一处理空指针判断。
  • 析构函数安全处理:freeMemory中先判断p是否为nullptr,避免访问空指针的下标,彻底解决valgrind检测到的错误。
  • 添加noexcept:移动构造和移动赋值运算符添加noexcept,符合C++标准对移动语义的要求(标准库容器在移动时会依赖noexcept判断是否使用移动而非拷贝)。
  • 成员函数空指针防护:setData和printData中添加p的非空判断,避免空指针访问导致的崩溃。
  • 修正main函数的错误用法:不再对已经被移动过的obj进行二次移动,改为移动持有有效数据的对象,同时演示被移动后的对象的行为。

内存泄漏验证

修复后的代码满足三/五法则,所有内存分配都对应释放:

  • 拷贝构造/赋值会深拷贝内存,析构时释放
  • 移动构造/赋值会转移内存所有权,源对象置空,不会重复释放
  • 析构函数安全处理空指针,不会出现内存泄漏或重复释放的问题

用valgrind检测时,不会再出现内存泄漏或非法访问的错误。

内容的提问来源于stack exchange,提问作者PANKAJ LADE

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 19:09:31