首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C++ 核心陷阱:非 POD 類型慎用 memcpy——淺拷貝與深拷貝的災難性後果

     編輯:關於C語言

問題引入

C 程式設計師習慣用 memcpy 複製陣列:


T src[N], dest[N];
memcpy(dest, src, N * sizeof(T));  // C 風格

在 C 中完美運作,但在 C++ 中若 T 為非基本類型,將引發淺拷貝災難:雙重釋放、記憶體洩漏、資料損壞。

何時不可用 memcpy

類型 T 滿足任一條件即禁止 memcpy:

  1. T 顯式定義了複製建構函式或賦值運算子
  2. T 未顯式定義,但其資料成員需要顯式複製/賦值(如成員含 std::string、std::vector、裸指標資源)

案例分析:裸指標成員


#include 
#include 
using namespace std;

/* 類別 B:管理動態記憶體,需深拷貝 */
class B {
    int *a;
public:
    B(int m = 0) { cout << "B建構\n"; a = new int; *a = m; }
    B(const B& b) { cout << "B複製建構\n"; a = new int; *a = *(b.a); }
    B& operator=(const B& b) {
        cout << "B賦值\n";
        if (&b != this) { delete a; a = new int; *a = *(b.a); }
        return *this;
    }
    ~B() { cout << "B解構\n"; delete a; }
    int* get_ptr() const { return a; }
    int get_val() const { return *a; }
};

/* 類別 A:未顯式定義複製/賦值/解構,但成員 B 需深拷貝 */
class A {
    B b;
public:
    A(int m = 0) : b(m) {}
    B& get_b() { return b; }
};

int main() {
    A a1(1), a2(2);
    cout << "a1: " << a1.get_b().get_ptr() << " " << a1.get_b().get_val() << endl;
    cout << "a2: " << a2.get_b().get_ptr() << " " << a2.get_b().get_val() << endl;

    a1 = a2;  // 編譯器生成的預設賦值會遞迴呼叫 B::operator=
    // memcpy(&a1, &a2, sizeof(A)); // 災難!位元複製導致 a1.b.a 指向 a2.b.a 同一塊記憶體

    cout << "賦值後 a1: " << a1.get_b().get_ptr() << " " << a1.get_b().get_val() << endl;
    cout << "賦值後 a2: " << a2.get_b().get_ptr() << " " << a2.get_b().get_val() << endl;

    A a3(1);
    A a4(a3);  // 複製建構遞迴呼叫 B 複製建構
    cout << "a3: " << a3.get_b().get_ptr() << " " << a3.get_b().get_val() << endl;
    cout << "a4: " << a4.get_b().get_ptr() << " " << a4.get_b().get_val() << endl;
    return 0;
}

執行結果對比

正確賦值/複製建構輸出:
B建構
B建構
a1: 0x... 1
a2: 0x... 2
B賦值
a1: 0x... 2   <-- 新記憶體,值獨立
a2: 0x... 2
B建構
B複製建構
a3: 0x... 1
a4: 0x... 1   <-- 新記憶體,值獨立
B解構 x4

若使用 memcpy(&a1, &a2, sizeof(A)):
a1.b.a 與 a2.b.a 指向同一地址
雙重 delete → heap corruption / crash
a1 修改值會影響 a2

標準規定與最佳實踐

類別類型TriviallyCopyable可用 memcpy建議
基本類型是是放心用
POD (Plain Old Data)是是C 風格結構體
含 std::string/vector否否必須用複製建構/賦值
含裸指標資源否否遵循 Rule of Three/Five
含虛函式/虛基類否否vptr 複製會破壞多型

現代 C++ 替代方案


// 1. 標準演算法(推薦)
#include 
#include 
vector dest = src;  // 或
copy(begin(src), end(src), begin(dest));

// 2. 陣列專用
array dest = src;  // C++11

// 3. 手寫迴圈(效能敏感且確認 TriviallyCopyable)
for (size_t i = 0; i < N; ++i) dest[i] = src[i];  // 呼叫複製建構/賦值

Rule of Three / Five / Zero 速查

  • Three:需析構函式 → 必須定義複製建構、賦值運算子
  • Five:C++11 另加移動建構、移動賦值
  • Zero:若成員皆為 RAII 類型,什麼都不用寫,編譯器生成的預設行為即正確

總結

memcpy 是位元級複製,不呼叫建構/解構/賦值。任何管理資源(記憶體、檔案、鎖、句柄)的類型,使用 memcpy 即為未定義行為。

養成習慣:std::copy、std::vector、std::array、RAII 智慧指標。將 memcpy 限制在 char 緩衝區、網路封包、硬體介面等純位元資料場景。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved