跳到主要内容 C++中 memcpy 和赋值拷贝的核心区别 | 极客日志
C++
C++中 memcpy 和赋值拷贝的核心区别 C++中 memcpy 是内存层面的二进制逐字节拷贝,属于 C 语言底层操作;赋值拷贝是 C++ 语法层面的逻辑拷贝,内置类型直接拷贝值,自定义类型调用拷贝构造或赋值运算符。memcpy 仅适用于平凡可拷贝类型,非平凡可拷贝类型(如含动态内存、虚函数)使用会导致崩溃。开发中应优先使用赋值拷贝以确保安全,仅在特定极致性能场景下使用 memcpy。
城市逃兵 发布于 2026/2/9 更新于 2026/4/18 1 浏览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:
没有自定义的拷贝构造函数、赋值运算符、析构函数 ;
所有成员变量都是平凡可拷贝类型;
没有虚函数/虚基类(虚函数会引入虚函数表指针,memcpy 拷贝会导致虚表指针混乱);
继承体系中的所有基类都是平凡可拷贝类型。
简单来说:没有自定义拷贝/析构逻辑、没有虚函数、成员都是内置类型的简单结构体/类 ,就是平凡可拷贝类型。
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 () {
int a = 10 , b = 0 ;
b = a;
memcpy (&b, &a, sizeof (int ));
Point p1 = {1 , 2 }, p2 = {0 , 0 };
p2 = p1;
memcpy (&p2, &p1, sizeof (Point));
cout << p2. x << "," << p2. y << endl;
return 0 ;
}
说明 :此场景下,memcpy 和赋值拷贝的效果完全一致 ,性能几乎没有区别(编译器对赋值拷贝的优化会和 memcpy 持平),实际开发中用赋值拷贝更简洁(无需写 sizeof)。
场景 2:自定义复杂类型(非平凡可拷贝,memcpy 拷贝直接崩溃) 以包含 std::string 的类 为例(std::string 是典型的非平凡可拷贝类型,内部有动态内存分配):
#include <cstring>
#include <iostream>
#include <string>
using namespace std;
struct Person {
string name;
int age;
};
int main () {
Person p1 = {"Tom" , 20 }, p2;
p2 = p1;
cout << "赋值拷贝:" << p2. name << "," << p2. age << endl;
Person p3;
memcpy (&p3, &p1, sizeof (Person));
cout << "memcpy 拷贝:" << p3. name << "," << p3. age << endl;
return 0 ;
}
std::string 内部维护了指向堆内存的指针 (存储字符串内容)、字符串长度、容量等成员;
memcpy 逐字节拷贝 Person 对象时,只是把 p1.name 中的堆指针值 复制到 p3.name,而非复制堆内存中的字符串(浅拷贝);
当 p1/p3 析构时,会两次释放同一块堆内存 (double free),直接导致程序崩溃;
而赋值拷贝 会触发 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;
b1. show ();
memcpy (&b2, &d, sizeof (Base));
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 (arr2, arr1, sizeof (arr1));
2. 超大块连续平凡可拷贝数据:memcpy 微优 当拷贝的内存块极大(如 100MB 以上的连续数组),memcpy 的底层实现(通常是汇编优化的块拷贝,如 x86 的 rep movsb 指令)会比编译器自动优化的赋值拷贝略快,因为 memcpy 是专门为内存拷贝设计的函数,无任何额外逻辑。
3. 自定义类型:赋值拷贝的性能取决于拷贝逻辑 如果自定义类型的拷贝构造/赋值运算符是深拷贝 (如 std::vector),赋值拷贝的性能远低于 memcpy(但此时 memcpy 不能用,否则崩溃);如果是浅拷贝 (平凡可拷贝类型),则和 memcpy 性能持平。
六、实际开发中的选择原则(避坑核心) 开发中到底该用 memcpy 还是赋值拷贝?遵循**'能用人赋值拷贝,就不用 memcpy'的原则,仅在 特定极致性能场景**下使用 memcpy,具体选择规则:
优先使用赋值拷贝 :
适用于所有场景 (内置/自定义类型),语法简洁、安全,符合 C++ 面向对象范式;
无需关心字节数、类型是否可拷贝,编译器会兜底做类型检查和逻辑处理;
自定义类型可通过重写拷贝构造/赋值运算符 ,灵活实现深拷贝/浅拷贝,避免内存问题。
仅在以下场景使用 memcpy :
拷贝的是平凡可拷贝类型 (内置类型/简单结构体);
拷贝的是大块连续的内存数据 (如超大数组、缓冲区),且对性能有极致要求 ;
底层 C 风格编程(如操作裸指针、缓冲区、网络数据解析),需要直接操作内存二进制流。
绝对禁止用 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 };
memcpy (arr, arr + 1 , 4 * sizeof (int ));
memmove (arr, arr + 1 , 4 * sizeof (int ));
总结 C++ 中 memcpy 和赋值拷贝的核心区别与使用原则,用 3 句话概括:
本质区别 :memcpy 是C 语言底层无类型的二进制逐字节拷贝 ,赋值拷贝是C++ 语法层面的有类型逻辑拷贝 (内置类型值拷贝,自定义类型调用拷贝函数);
安全边界 :memcpy 仅适用于平凡可拷贝类型 ,非平凡可拷贝类型(含动态内存/虚函数/自定义拷贝逻辑)用 memcpy 会导致崩溃/未定义行为,赋值拷贝则安全适配所有类型;
选择原则 :优先使用赋值拷贝 (简洁、安全、符合 C++ 范式),仅在大块连续平凡可拷贝数据的极致性能场景 下使用 memcpy,绝对避免在非平凡可拷贝类型上使用 memcpy。
新手最核心的避坑点:不要用 memcpy 拷贝 C++ 的自定义对象(尤其是 STL 容器、含虚函数/动态内存的类) ,赋值拷贝才是 C++ 的正确选择。
微信扫一扫,关注极客日志 微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具 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