第5.14章: 内存函数

English

概述

C 与 C++ 提供了一系列底层的内存操作函数: memcpy、memmove、memset、 memchr 和 memcmp。它们作用于原始字节, 完全忽略类型。

在本章中,你将学到:

需要的头文件

所有内存函数——memcpy、memmove、 memset、memcmp 与 memchr——都声明在 <cstring> 头文件中。使用其中任何一个之前,都必须包含该头文件。


    #include <cstring>
    

1. memcpy

memcpy 把原始字节从一个内存区域复制到另一个。


int src{42};
int dst{0};

::std::memcpy(::std::addressof(dst),
              ::std::addressof(src),
              sizeof(int));

调用之后,dst 含有与 src 相同的位。

1.1 memcpy 忽略类型

memcpy 不知道也不关心涉及什么类型。它只复制 字节。这就是它能安全用于类型双关的原因。

1.2 memcpy 是编译器魔法

现代编译器把 memcpy 当作内建函数。如果大小在编译期 已知且很小,编译器常常把它替换成几条 mov 指令。


memcpy(::std::addressof(dst), ::std::addressof(src), 4)  →  mov eax, [src]; mov [dst], eax

这就是 memcpy 极快的原因。

1.3 用 memcpy 做类型双关

memcpy 是(除 ::std::bit_cast 之外)唯一 对指针安全的、把一个对象的位重新解释为另一种类型的方式。


float f{1.0f};
std::uint32_t u{};

::std::memcpy(::std::addressof(u),
              ::std::addressof(f),
              sizeof(float));   // ✔ safe

这不违反严格别名规则,因为没有使用错误类型的指针。

1.4 memcpy 与 nullptr

用空指针配非零大小调用 memcpy 是未定义行为。


if (n != 0) {
    ::std::memcpy(dst, src, n);
}

调用 memcpy 之前始终检查 n != 0。

2. memmove

memmove 类似 memcpy,但对重叠的内存也安全。


::std::memmove(dst, src, n);

如果两个区域重叠,memcpy 是未定义行为,而 memmove 能正确处理。

2.1 memmove 也是编译器魔法

编译器把 memmove 当作内建函数。如果编译器能证明 两个区域不重叠,它可能把 memmove 优化成 memcpy,甚至简单的 mov 指令。

2.2 memmove 与 nullptr

用空指针配非零大小调用 memmove 是未定义行为。


if (n != 0) {
    ::std::memmove(dst, src, n);
}

3. memset

memset 用某个字节值填充一块内存。


int x{};
::std::memset(::std::addressof(x), 0, sizeof(int));

这把 x 的所有字节设为 0。

3.1 memset 也是编译器魔法

如果编译器在编译期知道大小,它常把 memset 替换成一系列 mov 指令。

4. memchr

memchr 在一块内存中搜索某个字节值。


unsigned char buffer[8]{1,2,3,4,5,6,7,8};

void *p = ::std::memchr(buffer, 4, 8);   // finds byte 4

在可能的情况下,memchr 同样被当作编译器内建函数。

5. memcmp

memcmp 逐字节比较两块内存。它不知道也不关心 类型、编码或空终止符。


int a{42};
int b{42};

int result = ::std::memcmp(::std::addressof(a),
                           ::std::addressof(b),
                           sizeof(int));

返回值是:

5.1 memcmp 忽略类型

memcmp 比较原始字节。填充字节、字节序和位 模式都会影响结果。

5.2 memcmp 是编译器魔法

编译器常把小而大小固定的 memcmp 调用优化成几条 cmp 指令。

5.3 memcmp 与 nullptr

用空指针配非零大小调用 memcmp 是未定义行为。


if (n != 0) {
    ::std::memcmp(p, q, n);
}

6. 延伸阅读:以空字符结尾的字节串

许多 C 库函数作用于以空字符结尾的字节串。 它们与本章中的原始字节函数不同。

完整概览见:

cppreference:以空字符结尾的字节串

7. 用 ::fast_io::secure_clear 安全擦除机密数据

处理密码、加密密钥或认证材料这类敏感数据时, 用完后安全地擦除内存非常重要。

一个常见错误是使用 memset:


char secret[32]{};
// ... fill secret ...
::std::memset(secret, 0, sizeof(secret));   // ❌ may be optimized away

编译器可能完全移除这段 memset,因为之后 再也不读取这块内存。

为解决这个问题,::fast_io 提供了:

::fast_io::secure_clear

该函数保证:

示例


char secret[32]{};
// ... fill secret with sensitive data ...

::fast_io::secure_clear(secret, sizeof(secret));   // ✔ securely erased

这是在内存中清除机密数据的正确方式。

要点