memcpy在实际工程中最常见的用法:内存打包/序列化(serialize)和解包/反序列化(deserialize),尤其适合结构体数组、网络传输、文件存储等场景
·
memcpy 在实际工程中最常见的用法:内存打包/序列化(serialize)和解包/反序列化(deserialize),尤其适合结构体数组、网络传输、文件存储等场景。
memcpy 核心要点
| 序号 | 要点 | 说明与注意事项 |
|---|---|---|
| 1 | 函数原型 | void* memcpy(void* dest, const void* src, size_t n); |
| 2 | 行为 | 从 src 开始拷贝 正好 n 个字节 到 dest(不关心类型,纯字节拷贝) |
| 3 | 重叠 | 不允许 src 和 dest 区域重叠,否则未定义行为(UB)。常见表现:数据错乱、崩溃、看似正常但隐藏 bug。重叠时必须用 memmove |
| 4 | 覆盖 | dest 原有数据会被覆盖(从开头起 n 字节)。想“追加”需手动调整 dest 指针 |
| 5 | 空指针 | dest/src 为 nullptr → 未定义行为。强烈建议 先判空 |
| 6 | 字符串 | 拷贝 n 字节时不自动加 ‘\0’。若想安全打印字符串,必须确保拷贝了 ‘\0’ 或手动补上 |
| 7 | sizeof(src) vs strlen(src)+1 | sizeof(数组) 包含 ‘\0’(如果初始化时有);strlen 不含。字符串拷贝推荐用 strlen(src)+1 |
| 8 | 性能 | 通常最快(底层可能用 SIMD、字拷贝等优化),但牺牲了重叠安全 |
| 9 | 替代品 | 重叠可能时 → 用 memmove;类型安全时 → std::copy / std::copy_n |
示例代码分析与改进建议
代码逻辑完全正确:结构体 + 基本类型 的连续打包 → 缓冲区 → 再按偏移解析。非常经典的二进制序列化手法。
原代码小问题/风险点:
- 没有检查指针是否为空(虽然这里 buffer 是栈上数组,不太可能空)
- 没有检查缓冲区是否足够大(硬编码 128,可能溢出)
- 使用了
_tmain和stdafx.h(老式 VS 项目风格,现在不推荐) system("pause")是 Windows 专用,不跨平台- 没有使用
const修饰只读数据
改进版(更安全、更现代 C++):
#include <iostream>
#include <cstring> // memcpy
#include <string>
struct STUDENT {
char name[32]; // 建议用 std::string 更安全,但这里保持原样
int age;
bool gender;
};
int main() {
STUDENT a = { "Li Lei", 20, true };
STUDENT b = { "Han MeiMei", 19, false };
constexpr size_t BUFFER_SIZE = 512; // 加大缓冲,防止溢出
char buffer[BUFFER_SIZE] = {0}; // 清零初始化(好习惯)
char* p = buffer;
size_t offset = 0;
// 打包(序列化)
if (p == nullptr) return 1; // 防御性检查
memcpy(p, &a, sizeof(a));
p += sizeof(a);
offset += sizeof(a);
int num1 = 100;
memcpy(p, &num1, sizeof(num1));
p += sizeof(num1);
offset += sizeof(num1);
memcpy(p, &b, sizeof(b));
p += sizeof(b);
offset += sizeof(b);
// 检查是否溢出(生产代码必须有)
if (offset > BUFFER_SIZE) {
std::cerr << "Buffer overflow!\n";
return 1;
}
// 解析(反序列化)
STUDENT c{}, d{};
int num2 = 0;
memcpy(&c, buffer, sizeof(STUDENT));
memcpy(&num2, buffer + sizeof(STUDENT), sizeof(int));
memcpy(&d, buffer + sizeof(STUDENT) + sizeof(int), sizeof(STUDENT));
// 输出(注意 name 可能没有 '\0',但这里初始化有,所以安全)
std::cout << "c: " << c.name << " " << c.age << " " << c.gender << "\n";
std::cout << "num2: " << num2 << "\n";
std::cout << "d: " << d.name << " " << d.age << " " << d.gender << "\n";
return 0;
}
更现代 C++ 替代方案(推荐新项目使用):
#include <vector>
#include <span> // C++20
#include <cstring>
std::vector<std::byte> serialize(const STUDENT& s, int num) {
std::vector<std::byte> buf;
buf.reserve(sizeof(STUDENT) * 2 + sizeof(int));
auto append = [&buf](const auto& data) {
auto bytes = std::as_bytes(std::span(&data, 1));
buf.insert(buf.end(), bytes.begin(), bytes.end());
};
append(s);
append(num);
append(s); // 示例用同一个 s
return buf;
}
- 优点:自动管理大小、无需手动偏移、类型更安全。
- C++20 后
std::span+std::as_bytes是最佳实践。
字符串拷贝的正确做法
char src[32] = "hello"; // 实际占 6 字节(含 '\0')
char dest[100] = "old data here";
// 正确:拷贝含 '\0'
memcpy(dest, src, sizeof(src)); // dest → "hello\0data here"
std::cout << dest << "\n"; // 输出 "hello"
// 错误示范:不拷贝 '\0'
memcpy(dest, src, 5); // dest → "helloold data here"
std::cout << dest << "\n"; // 输出 "helloold data here"(可能乱码或超长)
// 安全补救
memcpy(dest, src, 5);
dest[5] = '\0'; // 手动结束
推荐字符串拷贝:用 strncpy(但小心截断)或 std::string。
总结:什么时候用 memcpy?
- 用 memcpy:明确知道不重叠、追求最高性能、拷贝 POD/平凡类型结构体
- 用 memmove:可能重叠(如数组内移位、字符串插入)
- 用 std::copy / std::copy_n:有类型、迭代器风格、C++ 项目
- 用序列化库(protobuf、flatbuffers、nlohmann/json 等):复杂数据结构、版本兼容、网络传输
有具体场景想优化吗?比如:
- 网络发包时如何加长度前缀?
- 如何处理变长字符串?
- 如何用 memcpy 实现结构体对齐/字节序转换?
更多推荐


所有评论(0)