第5.15章: 常见指针陷阱
概述
指针很强大,但极其容易被误用。C++ 程序中的许多缺陷 都来自对指针有效性、生命周期、别名、对齐以及算术的错误假设。 本章汇总最常见的错误,以便你能避开它们。
你将学到:
- 悬垂指针
- 空指针误用
- 差一错误
- 未对齐的指针
- 严格别名违规
- 内存重叠的危险
- 指针算术错误
- 生命周期问题
- 类型双关错误
- 内存函数的错误使用
- 指针比较陷阱
1. 悬垂指针
悬垂指针指向一个已经不存在的对象。
int const *p;
{
int x{42};
p = ::std::addressof(x);
} // x is destroyed here
// *p; // ❌ dangling pointer
指针只在它所指向的对象存活期间有效。
2. 空指针误用
解引用 nullptr 永远是未定义行为。
int *p{nullptr};
// *p = 10; // ❌ undefined behavior
另外,把 nullptr 配非零大小传给 memcpy、memmove、
memcmp 或 memchr 也是未定义
行为(见第5.14章)。
3. 差一错误
指针算术以所指类型为单位移动。
int a[3]{1,2,3};
int *p = a;
int *end = a + 3; // one past the end (valid but not dereferenceable)
// *end; // ❌ undefined behavior
只有区间 [a, a + 3) 可用于解引用。
4. 未对齐的指针
某些类型要求比其他类型更严格的对齐。把指针转换为对齐要求 更严格的类型可能导致未定义行为。
unsigned char buffer[8]{};
// int *p = reinterpret_cast(buffer + 1); // ❌ misaligned
对大多数类型,未对齐访问是不允许的。
5. 严格别名违规
通过不相关类型的指针访问对象是未定义 行为(见第5.13章)。
float f{1.0f};
int *p = reinterpret_cast(::std::addressof(f));
// *p; // ❌ strict aliasing violation
6. 内存重叠的危险
对重叠的内存使用 memcpy 是未定义行为。
// memcpy(dst, src, n); // ❌ if ranges overlap
不确定时请使用 memmove。
7. 指针算术错误
指针算术以所指类型为单位,而不是以字节为单位。
int a[3]{1,2,3};
int *p = a;
int *q = p + 1; // moves by sizeof(int) bytes
另外,sizeof(pointer) 不是所指对象的大小。
8. 生命周期问题
指针并不会延长它所指向对象的生命周期。
int const *p;
{
int x{10};
p = ::std::addressof(x);
}
// *p; // ❌ x is gone
9. 类型双关错误
用 reinterpret_cast 做类型双关几乎总是未定义
行为。请改用 memcpy 或 ::std::bit_cast。
10. 内存函数的错误使用
常见错误包括:
- 忘记检查
size != 0 - 用非零大小搭配
nullptr - 对重叠内存使用
memcpy - 用
memset清除机密数据(可能被优化掉)
11. 指针比较陷阱
顺序比较(<、>)只对指向同一数组的
指针有效。
// p < q; // ❌ undefined behavior if p and q are unrelated
要点
- 指针只在它所指向的对象存活期间有效。
- 永远不要解引用
nullptr。 - 指针算术很容易被误用。
- 未对齐与严格别名违规会导致未定义行为。
- 重叠内存请使用
memmove。 - 类型双关请使用
memcpy或::std::bit_cast。 - 擦除机密数据请使用
::fast_io::secure_clear。 - 指针比较只在同一数组内有效。