p
指向的内存被释放后,不能直接使用它的值。同样地,未初始化指针的值也有相同的状态:即使只是为了复制而读取它,也会导致未定义的行为。
这个令人惊讶的限制的原因是可能存在陷阱表示法。释放
p
所指向的内存可能会使其值成为一个陷阱表示法。
我记得在上世纪90年代初曾经有一个目标机,它的行为就是这样的。当时不是嵌入式系统,而是广泛使用的Windows 2.x。它使用Intel架构的16位保护模式,在这种模式下,指针宽度为32位,具有16位选择器和16位偏移量。为了访问内存,需要使用特定的指令将指针加载到一对寄存器中(一个段寄存器和一个地址寄存器)。
LES BX,[BP+4] ; load pointer into ES:BX
将指针值的选择器部分加载到段寄存器中会具有验证选择器值的副作用:如果选择器没有指向有效的内存段,就会触发异常。
编译看似无害的语句q = p;
可以以许多不同的方式编译:
MOV AX,[BP+4]
MOV DX,[BP+6]
MOV [BP-6],AX
MOV [BP-4],DX
或者
LES BX,[BP+4] ; loading via ES:BX registers: side effects
MOV [BP-6],BX
MOV [BP-4],ES
第二个选择有两个优点:
释放内存可能会取消映射段并使选择器无效。该值成为trap值,并在某些体系结构上将其加载到
ES:BX
中会触发异常,也称为
陷阱。
并非所有编译器都会仅使用
LES
指令来复制指针值,因为它更慢,但一些编译器会在指示生成紧凑代码时使用它,当时通常是常见选择,因为内存相对昂贵且稀缺。
C标准允许这样做,并描述了一种未定义行为的代码形式,其中:
使用了对象生命周期已结束的指针的值(6.2.4)。
因为按照这种方式定义,此值变得不确定:
3.19.2 不确定的值:未指定的值或陷阱表示
但请注意,您仍然可以通过字符类型进行别名处理来操作该值:
unsigned char *pc = (unsigned char*)&p;
size_t i;
for (i = 0; i < sizeof(p); i++)
printf("%02X", pc[i]);
memcpy(&q, &p, sizeof(p));
p
是堆栈上的本地变量,但它指向堆。如果在代码片段之后解引用q
,则会出现未定义行为。 - Basile Starynkevitchunsigned int
。但即使是unsigned
也可能会被误用。 - MSalters