の値はp
、それが指すメモリが解放された後はそのまま使用できません。より一般的には、初期化されていないポインターの値は同じステータスを持ちます。コピーする目的でそれを読み取るだけでも、未定義の動作が呼び出されます。
この驚くべき制限の理由は、トラップ表現の可能性です。が指すメモリを解放するp
と、その値がトラップ表現になる可能性があります。
1990 年代初頭に、このように振る舞ったそのような標的の 1 つを覚えています。当時は組み込みターゲットではなく、むしろ広く使用されていました: Windows 2.x. Intel アーキテクチャを 16 ビット プロテクト モードで使用し、ポインターは 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
2 番目のオプションには 2 つの利点があります。
メモリを解放すると、セグメントのマップが解除され、セレクタが無効になる場合があります。値はトラップ値になり、それを にロードすると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 */