在它指向的内存被释放后,它的值p
就不能这样使用了。更一般地,未初始化指针的值具有相同的状态:即使只是为了复制而读取它也会调用未定义的行为。
这种令人惊讶的限制的原因是陷阱表示的可能性。释放 指向的内存p
可以使其值成为陷阱表示。
我记得有一个这样的目标,早在 1990 年代初期就是这样。当时不是嵌入式目标,而是广泛使用:Windows 2.x。它在 16 位保护模式下使用 Intel 架构,其中指针为 32 位宽,具有 16 位选择器和 16 位偏移量。为了访问内存,使用特定指令将指针加载到一对寄存器(段寄存器和地址寄存器)中:
LES BX,[BP+4] ; load pointer into ES:BX
将指针值的选择器部分加载到段寄存器中会产生验证选择器值的副作用:如果选择器没有指向有效的内存段,则会引发异常。
编译看起来很无辜的语句q = p;
可以用许多不同的方式编译:
MOV AX,[BP+4] ; loading via DX:AX registers: no side effects
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
第二种选择有两个优点:
释放内存可能会取消映射段并使选择器无效。该值成为陷阱值并将其加载到ES:BX
触发异常中,在某些体系结构上也称为陷阱。
并非所有编译器都会使用该LES
指令来仅复制指针值,因为它速度较慢,但有些编译器在被指示生成紧凑代码时会这样做,这是当时的常见选择,因为内存相当昂贵且稀缺。
C 标准允许这样做,并在代码中描述了一种未定义的行为形式,其中:
使用指向生命周期已结束的对象的指针的值 (6.2.4)。
因为这个值已经变得不确定,定义如下:
3.19.2 不确定值:未指定的值或陷阱表示
但是请注意,您仍然可以通过字符类型通过别名来操作该值:
/* dumping the value of the free'd pointer */
unsigned char *pc = (unsigned char*)&p;
size_t i;
for (i = 0; i < sizeof(p); i++)
printf("%02X", pc[i]); /* no problem here */
/* copying the value of the free'd pointer */
memcpy(&q, &p, sizeof(p)); /* no problem either */