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

C++ 陷阱:為何非 POD 類型絕不可用 memcpy

     編輯:關於C

核心結論

memcpy 執行「位元級淺拷貝」,僅複製記憶體比特模式。 若類型管理動態資源(指標、檔案句柄、鎖等)或包含此類成員,memcpy 會導致雙重釋放、資源洩漏、邏輯錯誤等未定義行為。

何時不可用 memcpy?

符合以下任一條件即為「非平凡可複製類型」,禁用 memcpy:

  1. 類別顯式宣告複製建構函式、複製賦值運算子、解構函式(Rule of Three/Five)。
  2. 類別未顯式宣告,但其非靜態資料成員屬於上述類型(遞歸定義)。
  3. 類別擁有虛函數或虛基類別(含虛表指標,淺拷貝會破壞多態)。
  4. 類別為標準庫容器、std::string、智慧指標等資源管理型別。

經典反面教材:雙重釋放崩潰


#include 
#include   // memcpy

class Resource {
public:
    int* data;
    Resource(int v = 0) { data = new int(v); std::cout << "Resource ctor\n"; }
    ~Resource() { delete data; std::cout << "Resource dtor\n"; }
    // 隱式複製建構/賦值 = 淺拷貝(雙指標指向同一記憶體)
};

class Wrapper {
public:
    Resource res;  // 成員為非 POD
    Wrapper(int v = 0) : res(v) {}
    // 編譯器隱式產生的複製/賦值會正確複製 res(呼叫 Resource 的複製建構)
    // 但 memcpy 會繞過此機制!
};

int main() {
    Wrapper a(1), b(2);
    std::cout << "Before memcpy: a.res.data=" << a.res.data << " val=" << *a.res.data
              << " b.res.data=" << b.res.data << " val=" << *b.res.data << "\n";

    // 災難發生:位元拷貝,a.res.data 指標被 b.res.data 覆蓋
    std::memcpy(&a, &b, sizeof(Wrapper));

    std::cout << "After memcpy:  a.res.data=" << a.res.data << " val=" << *a.res.data
              << " b.res.data=" << b.res.data << " val=" << *b.res.data << "\n";

    // 離開作用域:a、b 依序解構 → 同一記憶體 delete 兩次 → 崩潰/堆損壞
    return 0;
}

輸出示例:

Resource ctor
Resource ctor
Before memcpy: a.res.data=0x55a3c2c0 val=1 b.res.data=0x55a3c2e0 val=2
After memcpy:  a.res.data=0x55a3c2e0 val=2 b.res.data=0x55a3c2e0 val=2
Resource dtor   // b 先解構,釋放 0x55a3c2e0
Resource dtor   // a 再解構,再次釋放 0x55a3c2e0 → 雙重釋放,程式崩潰

正確做法:使用複製建構/賦值


Wrapper a(1), b(2);
a = b;  // 呼叫 Wrapper::operator= → Resource::operator= → 深拷貝,安全
// 或
Wrapper c = b;  // 複製建構,安全

現代 C++ 判斷工具:Type Traits


#include 

static_assert(std::is_trivially_copyable_v == false, "Wrapper 非 trivially copyable");
static_assert(std::is_trivially_copyable_v == true, "int 可 memcpy");
static_assert(std::is_trivially_copyable_v == false, "string 內含指標");
  • std::is_trivially_copyable:可安全用 memcpy(等價於 C 的 POD 型別)。
  • std::is_trivial + std::is_standard_layout → C++11 起可判斷 POD。

最佳實踐清單

場景推薦做法禁忌
結構體僅含基本型別memcpy / std::copy / 直接賦值—
含 std::string/容器/智慧指標直接賦值 / std::movememcpy
自訂資源管理類別實作 Rule of Five,用賦值/建構memcpy
序列化/網路傳輸逐欄位序列化 / Protobuf / FlatBuffers直接 memcpy 物件
效能關鍵路徑確認 is_trivially_copyable 後用 memcpy未驗證就用

總結

  • memcpy 不知道 C++ 物件語義,只搬運位元。
  • 任何管理資源(記憶體、檔案、鎖、參考計數)的類型,**必須**透過複製建構/賦值運算子正確轉移擁有權。
  • 現代 C++ 提供 std::is_trivially_copyable 編譯期檢查,請善用靜態斷言防呆。
  • 寧可多打幾個字 a = b;,不可冒險用 memcpy(&a, &b, sizeof(a))。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved