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,可能溢出)
  • 使用了 _tmainstdafx.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 实现结构体对齐/字节序转换?
Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐