跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++

C++中 memcpy 和赋值拷贝的核心区别

C++中 memcpy 是内存层面的二进制逐字节拷贝,属于 C 语言底层操作;赋值拷贝是 C++ 语法层面的逻辑拷贝,内置类型直接拷贝值,自定义类型调用拷贝构造或赋值运算符。memcpy 仅适用于平凡可拷贝类型,非平凡可拷贝类型(如含动态内存、虚函数)使用会导致崩溃。开发中应优先使用赋值拷贝以确保安全,仅在特定极致性能场景下使用 memcpy。

城市逃兵发布于 2026/2/9更新于 2026/5/2420 浏览

C++中 memcpy 和赋值拷贝的核心区别,简单来说:二者是完全不同层面的拷贝方式——memcpy 是内存层面的二进制逐字节拷贝,属于 C 语言的底层内存操作;赋值拷贝是C++ 语法层面的拷贝,会根据数据类型触发对应的拷贝逻辑(内置类型直接拷贝值,自定义类型调用拷贝构造/赋值运算符)。

二者的核心差异体现在拷贝逻辑、适用类型、安全性、面向的编程范式上,memcpy 更偏向'裸内存操作',灵活但危险;赋值拷贝是 C++ 的原生语法,贴合面向对象特性,安全且符合语言规范。下面从核心区别、适用场景、安全性、性能、典型示例五个维度讲透,同时明确哪些场景绝对不能用 memcpy(新手最容易踩坑的点)。

一、核心底层逻辑(最本质区别)

先从底层原理理解二者的不同,这是所有差异的根源:

1. memcpy:二进制逐字节'硬拷贝'

memcpy 是 C 标准库 <cstring> 中的函数,函数原型:

void* memcpy(void* dest, const void* src, size_t n);
  • 核心行为:从 src 指向的内存地址开始,逐字节把 n 个字节的二进制数据,复制到 dest 指向的内存地址,完全不关心内存中存储的是什么数据类型;
  • 操作层面:直接操作内存地址和二进制流,属于无类型的底层内存操作,编译器不会做任何类型检查或逻辑处理;
  • 本质:把一块内存的'二进制快照'原封不动复制到另一块内存。
2. 赋值拷贝:语法层面的'逻辑拷贝'

赋值拷贝分两种场景(本质都是 C++ 语法的原生行为):

  • 内置类型(int/char/float/指针等):直接值拷贝(底层也是逐字节拷贝,但由编译器自动完成,封装了内存操作);
  • 自定义类型(class/struct):触发 C++ 的拷贝构造函数(初始化时,如 A a = b;)或赋值运算符重载(已初始化的赋值,如 a = b;),执行程序员定义的逻辑拷贝(可深拷贝、浅拷贝,或自定义其他逻辑)。
  • 操作层面:面向数据类型/对象,编译器会根据变量的类型,自动选择对应的拷贝逻辑,属于 C++面向对象范式的一部分;
  • 本质:按'类型规则'拷贝数据,而非单纯的内存二进制拷贝。
二、核心区别对比表

为了直观理解,用表格总结二者在所有关键维度的差异,这是实际开发中选择的核心依据:

对比维度memcpy赋值拷贝
所属范式C 语言,底层内存操作C++ 语法,面向类型/对象
拷贝逻辑无类型,二进制逐字节拷贝 n 个字节有类型,内置类型值拷贝/自定义类型调用拷贝函数
适用类型仅适用于平凡可拷贝类型所有 C++ 数据类型(内置/自定义)
类型检查无(void*接收任意指针,编译器不检查)严格类型检查(类型不匹配编译报错)
安全性低(手动控制字节数,易越界/浅拷贝坑)高(编译器兜底,自定义类型可手动控制深/浅拷贝)
是否触发函数不触发任何构造/析构/重载函数自定义类型触发拷贝构造/赋值运算符
使用成本高(需手动计算拷贝字节数 n)低(编译器自动处理,直接用=)
性能极致高效(纯内存操作,无额外开销)内置类型与 memcpy 持平,自定义类型取决于拷贝逻辑
错误来源手动传参错误(n 过大/过小、地址重叠)自定义类型的拷贝函数逻辑错误(如浅拷贝导致野指针)
三、关键概念:平凡可拷贝类型(memcpy 的唯一安全适用类型)

上面表格中提到 memcpy 仅适用于平凡可拷贝类型(Trivially Copyable Type),这是 C++ 标准定义的概念,也是新手最容易踩坑的点——非平凡可拷贝类型用 memcpy 拷贝会直接导致程序崩溃/未定义行为。

1. 什么是平凡可拷贝类型?

满足以下条件的类型,称为平凡可拷贝类型:

  • 内置类型:int/char/short/long/float/double/指针 等,都是平凡可拷贝类型;
  • 自定义 struct/class:
    1. 没有自定义的拷贝构造函数、赋值运算符、析构函数;
    2. 所有成员变量都是平凡可拷贝类型;
    3. 没有虚函数/虚基类(虚函数会引入虚函数表指针,memcpy 拷贝会导致虚表指针混乱);
    4. 继承体系中的所有基类都是平凡可拷贝类型。

简单来说:没有自定义拷贝/析构逻辑、没有虚函数、成员都是内置类型的简单结构体/类,就是平凡可拷贝类型。

2. 非平凡可拷贝类型(绝对不能用 memcpy)

典型的非平凡可拷贝类型:

  • 包含动态内存分配的类(如 std::string、std::vector、自定义的链表/树类);
  • 有自定义拷贝构造/赋值运算符/析构函数的类;
  • 包含虚函数/虚基类的类;
  • 嵌套了非平凡可拷贝类型的结构体/类。
四、典型示例:正确使用 vs 错误使用(新手必看)

通过代码示例,直观感受二者的使用场景和错误后果,分为内置类型/简单结构体(memcpy 和赋值拷贝都可用)、自定义复杂类型(仅赋值拷贝可用,memcpy 必错)两个场景。

场景 1:内置类型/简单平凡可拷贝结构体(二者都安全,性能持平)
#include <cstring>
#include <iostream>
using namespace std;

// 简单结构体:无自定义拷贝/析构,无虚函数,成员都是内置类型→平凡可拷贝类型
struct Point {
    int x;
    int y;
};

int main() {
    // 1. 内置类型
    int a = 10, b = 0;
    b = a; // 赋值拷贝(值拷贝)
    memcpy(&b, &a, sizeof(int)); // memcpy 拷贝(逐字节拷贝 4 个字节),与赋值拷贝效果一致

    // 2. 简单结构体
    Point p1 = {1, 2}, p2 = {0, 0};
    p2 = p1; // 赋值拷贝(编译器自动逐成员值拷贝)
    memcpy(&p2, &p1, sizeof(Point)); // memcpy 拷贝(逐字节拷贝 8 个字节),与赋值拷贝效果一致

    cout << p2.x << "," << p2.y << endl; // 输出 1,2,两种方式都正确
    return 0;
}

说明:此场景下,memcpy 和赋值拷贝的效果完全一致,性能几乎没有区别(编译器对赋值拷贝的优化会和 memcpy 持平),实际开发中用赋值拷贝更简洁(无需写 sizeof)。

场景 2:自定义复杂类型(非平凡可拷贝,memcpy 拷贝直接崩溃)

以包含 std::string 的类为例(std::string 是典型的非平凡可拷贝类型,内部有动态内存分配):

#include <cstring>
#include <iostream>
#include <string>
using namespace std;

// 复杂类:包含 std::string→非平凡可拷贝类型
struct Person {
    string name;
    int age;
    // 编译器自动生成拷贝构造/赋值运算符(深拷贝,会复制 string 的动态内存)
};

int main() {
    Person p1 = {"Tom", 20}, p2;
    // 方式 1:赋值拷贝(正确!触发编译器生成的赋值运算符,深拷贝 string)
    p2 = p1;
    cout << "赋值拷贝:" << p2.name << "," << p2.age << endl; // 输出 Tom,20

    // 方式 2:memcpy 拷贝(错误!未定义行为,大概率程序崩溃/野指针)
    Person p3;
    memcpy(&p3, &p1, sizeof(Person));
    cout << "memcpy 拷贝:" << p3.name << "," << p3.age << endl; // 崩溃/乱码
    return 0;
}

错误原因解析:

  1. std::string 内部维护了指向堆内存的指针(存储字符串内容)、字符串长度、容量等成员;
  2. memcpy 逐字节拷贝 Person 对象时,只是把 p1.name 中的堆指针值复制到 p3.name,而非复制堆内存中的字符串(浅拷贝);
  3. 当 p1/p3 析构时,会两次释放同一块堆内存(double free),直接导致程序崩溃;
  4. 而赋值拷贝会触发 std::string 的赋值运算符重载,执行深拷贝(重新分配堆内存,复制字符串内容),避免了浅拷贝问题。
场景 3:含虚函数的类(memcpy 拷贝导致虚表指针混乱)
#include <cstring>
#include <iostream>
using namespace std;

class Base {
public:
    virtual void show() {
        cout << "Base" << endl;
    } // 虚函数,有虚表指针
    int a = 10;
};

class Derived : public Base {
public:
    void show() override {
        cout << "Derived" << endl;
    }
    int b = 20;
};

int main() {
    Derived d;
    Base b1, b2;
    b1 = d; // 赋值拷贝(正确!切片,虚表指针指向 Derived 的虚表)
    b1.show(); // 输出 Derived,正确

    memcpy(&b2, &d, sizeof(Base)); // 错误!memcpy 拷贝虚表指针,导致未定义行为
    b2.show(); // 崩溃/乱输出,虚表指针混乱
    return 0;
}

错误原因:含虚函数的类会有一个虚表指针(vptr),指向类的虚函数表(vtable);memcpy 直接拷贝虚表指针的二进制值,会导致目标对象的虚表指针指向非法地址,调用虚函数时触发未定义行为。

五、性能对比:memcpy 是否一定更快?

很多人认为 memcpy 是底层操作,性能一定比赋值拷贝好,其实大部分场景下二者性能持平,只有超大块连续平凡可拷贝数据时,memcpy 才有微弱优势。

1. 内置类型/简单结构体:性能一致

编译器对 C++ 的赋值拷贝有极致优化(如 GCC/Clang 的 -O2/-O3 优化),会将内置类型的连续赋值拷贝直接优化为底层内存拷贝,和 memcpy 的汇编代码完全一致,性能没有区别。

比如:

int arr1[10000] = {1, 2, 3...};
int arr2[10000] = {0};
arr2 = arr1; // 赋值拷贝,编译器优化为 memcpy 级别的内存操作
memcpy(arr2, arr1, sizeof(arr1)); // 与上面性能一致
2. 超大块连续平凡可拷贝数据:memcpy 微优

当拷贝的内存块极大(如 100MB 以上的连续数组),memcpy 的底层实现(通常是汇编优化的块拷贝,如 x86 的 rep movsb 指令)会比编译器自动优化的赋值拷贝略快,因为 memcpy 是专门为内存拷贝设计的函数,无任何额外逻辑。

3. 自定义类型:赋值拷贝的性能取决于拷贝逻辑

如果自定义类型的拷贝构造/赋值运算符是深拷贝(如 std::vector),赋值拷贝的性能远低于 memcpy(但此时 memcpy 不能用,否则崩溃);如果是浅拷贝(平凡可拷贝类型),则和 memcpy 性能持平。

六、实际开发中的选择原则(避坑核心)

开发中到底该用 memcpy 还是赋值拷贝?遵循**'能用人赋值拷贝,就不用 memcpy'的原则,仅在特定极致性能场景**下使用 memcpy,具体选择规则:

  1. 优先使用赋值拷贝:
    • 适用于所有场景(内置/自定义类型),语法简洁、安全,符合 C++ 面向对象范式;
    • 无需关心字节数、类型是否可拷贝,编译器会兜底做类型检查和逻辑处理;
    • 自定义类型可通过重写拷贝构造/赋值运算符,灵活实现深拷贝/浅拷贝,避免内存问题。
  2. 仅在以下场景使用 memcpy:
    • 拷贝的是平凡可拷贝类型(内置类型/简单结构体);
    • 拷贝的是大块连续的内存数据(如超大数组、缓冲区),且对性能有极致要求;
    • 底层 C 风格编程(如操作裸指针、缓冲区、网络数据解析),需要直接操作内存二进制流。
  3. 绝对禁止用 memcpy 的场景:
    • 拷贝非平凡可拷贝类型(含 std::string/std::vector、自定义拷贝逻辑、虚函数的类);
    • 拷贝的内存地址存在重叠(此时应用 memmove,而非 memcpy,memcpy 不处理地址重叠);
    • 无法准确计算拷贝的字节数 n(n 过大导致内存越界,n 过小导致拷贝不完整)。
七、补充:memcpy 与 memmove 的区别(避免地址重叠坑)

新手容易混淆 memcpy 和 memmove,这里简单补充:

  • memcpy:不处理内存地址重叠,如果 dest 和 src 的内存区域有重叠,会导致拷贝数据错乱;
  • memmove:处理内存地址重叠,内部会先把 src 的数据拷贝到临时缓冲区,再复制到 dest,避免错乱;
  • 性能:memcpy 略快于 memmove(无临时缓冲区开销),无地址重叠时用 memcpy,有重叠时必须用 memmove。

示例:

int arr[5] = {1, 2, 3, 4, 5};
// 地址重叠:src=arr+1,dest=arr,拷贝 4 个 int
memcpy(arr, arr + 1, 4 * sizeof(int)); // 数据错乱,未定义行为
memmove(arr, arr + 1, 4 * sizeof(int)); // 正确,arr 变为{2,3,4,5,5}
总结

C++ 中 memcpy 和赋值拷贝的核心区别与使用原则,用 3 句话概括:

  1. 本质区别:memcpy 是C 语言底层无类型的二进制逐字节拷贝,赋值拷贝是C++ 语法层面的有类型逻辑拷贝(内置类型值拷贝,自定义类型调用拷贝函数);
  2. 安全边界:memcpy 仅适用于平凡可拷贝类型,非平凡可拷贝类型(含动态内存/虚函数/自定义拷贝逻辑)用 memcpy 会导致崩溃/未定义行为,赋值拷贝则安全适配所有类型;
  3. 选择原则:优先使用赋值拷贝(简洁、安全、符合 C++ 范式),仅在大块连续平凡可拷贝数据的极致性能场景下使用 memcpy,绝对避免在非平凡可拷贝类型上使用 memcpy。

新手最核心的避坑点:不要用 memcpy 拷贝 C++ 的自定义对象(尤其是 STL 容器、含虚函数/动态内存的类),赋值拷贝才是 C++ 的正确选择。

目录

  1. 一、核心底层逻辑(最本质区别)
  2. 1. memcpy:二进制逐字节“硬拷贝”
  3. 2. 赋值拷贝:语法层面的“逻辑拷贝”
  4. 二、核心区别对比表
  5. 三、关键概念:平凡可拷贝类型(memcpy 的唯一安全适用类型)
  6. 1. 什么是平凡可拷贝类型?
  7. 2. 非平凡可拷贝类型(绝对不能用 memcpy)
  8. 四、典型示例:正确使用 vs 错误使用(新手必看)
  9. 场景 1:内置类型/简单平凡可拷贝结构体(二者都安全,性能持平)
  10. 场景 2:自定义复杂类型(非平凡可拷贝,memcpy 拷贝直接崩溃)
  11. 场景 3:含虚函数的类(memcpy 拷贝导致虚表指针混乱)
  12. 五、性能对比:memcpy 是否一定更快?
  13. 1. 内置类型/简单结构体:性能一致
  14. 2. 超大块连续平凡可拷贝数据:memcpy 微优
  15. 3. 自定义类型:赋值拷贝的性能取决于拷贝逻辑
  16. 六、实际开发中的选择原则(避坑核心)
  17. 七、补充:memcpy 与 memmove 的区别(避免地址重叠坑)
  18. 总结
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 基于 Rust 与 GLM-5 的高性能 AI 翻译 CLI 工具实现
  • 宇树机器人 G1 二次开发:FAST-LIO 建图配置与 RViz 可视化
  • 基于 Java 的家庭药品管理系统设计与实现
  • Spring Cloud Gateway 过滤器工厂详解
  • Python 开发者兼职接单指南:核心技术、能力标准与学习路径
  • AI 绘画提示词生成器工具:原理剖析与工程实践
  • 2024 年转行 AI 产品经理的时机与路径分析
  • 贪心算法核心思想与 LeetCode 经典例题解析
  • ESLint 从原理到实践:构建高质量 JavaScript/TypeScript 代码
  • 配置 OpenClaw 机器人通过钉钉调用 OpenMetadata
  • 用 QQ 私聊打造全自动化运维助手
  • AIGC 情感化智能客服实战:投诉率优化技术方案
  • Android 离线语音识别实践:基于 Whisper 与 TensorFlow Lite 实现本地转录
  • 绿联 NAS 配置 WebDAV 公网访问并使用 RaiDrive 挂载到本地
  • Python 基础语法核心指南
  • OpenClaw AI 代理应用场景与核心实现
  • 前端实现视频画中画功能 - 主窗口与小窗同步控制
  • 基于 Ollama 在本地电脑部署和运行大语言模型指南
  • ToDesk ToClaw 评测:零门槛体验 OpenClaw 级 AI 自动化
  • C++ 图论基础与遍历算法实战

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online