第5.14章: 内存函数
概述
C 与 C++ 提供了一系列底层的内存操作函数:
memcpy、memmove、memset、
memchr 和 memcmp。它们作用于原始字节,
完全忽略类型。
在本章中,你将学到:
- 每个函数如何工作
- 为什么编译器把它们当作内建函数(intrinsics)或“魔法”
- 为什么平凡的
memcpy与memmove常被优化成简单的mov指令 - 如何用
memcpy进行安全的类型双关 - 为什么用
nullptr配非零大小调用这些函数是未定义行为 - 为什么在调用它们之前必须检查
size != 0 - 如何用
::fast_io::secure_clear安全地擦除机密数据 - 到哪里进一步阅读以空字符结尾的字节串
需要的头文件
所有内存函数——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));
返回值是:
0如果所有字节都相等< 0如果a中第一个不同的字节更小> 0如果a中第一个不同的字节更大
5.1 memcmp 忽略类型
memcmp 比较原始字节。填充字节、字节序和位
模式都会影响结果。
5.2 memcmp 是编译器魔法
编译器常把小而大小固定的 memcmp 调用优化成几条
cmp 指令。
5.3 memcmp 与 nullptr
用空指针配非零大小调用 memcmp 是未定义行为。
if (n != 0) {
::std::memcmp(p, q, n);
}
6. 延伸阅读:以空字符结尾的字节串
许多 C 库函数作用于以空字符结尾的字节串。 它们与本章中的原始字节函数不同。
完整概览见:
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
这是在内存中清除机密数据的正确方式。
要点
memcpy、memmove、memset、memchr与memcmp都作用于原始字节。- 编译器把这些函数当作内建函数或“魔法”。
- 平凡的
memcpy与memmove常被优化成简单的mov指令。 memcpy是类型双关位的一种安全方式(与::std::bit_cast并列)。- 调用这些函数之前始终检查
n != 0。 - 用
nullptr配非零大小调用它们是未定义行为。 memmove是memcpy对重叠内存的安全版本。::fast_io::secure_clear是擦除机密数据的正确方式。- 以空字符结尾的字节串函数见 cppreference。