第6章:厳密なエイリアシングと有効型規則
この章で学ぶこと
- 厳密なエイリアシング規則の定義
- 有効型の決定方法
- 許可されるアクセス
- 型パニングの正しい方法
- コンパイラオプション
---
6.1 厳密なエイリアシング規則
規格上の定義
規格 6.5p7:
> An object shall have its stored value accessed only through an lvalue expression that has one of the following types: > - a type compatible with the effective type of the object, > - a qualified version of a type compatible with the effective type, > - a type that is the signed or unsigned type corresponding to the effective type, > - a type that is the signed or unsigned type corresponding to a qualified version of the effective type, > - an aggregate or union type that includes one of the aforementioned types among its members, or > - a character type.
違反例
int i = 42;
float *fp = (float *)&i;
float f = *fp; /* 未定義動作:int を float でアクセス */
許可されるアクセス
int i = 42;
/* 互換型 */
int *ip = &i;
int x = *ip; /* OK */
/* 符号の異なる対応型 */
unsigned int *uip = (unsigned int *)&i;
unsigned int y = *uip; /* OK */
/* 文字型 */
unsigned char *cp = (unsigned char *)&i;
unsigned char c = *cp; /* OK */
---
6.2 有効型の決定
宣言型を持つオブジェクト
int x; /* 有効型: int */
動的確保されたオブジェクト
void *p = malloc(sizeof(int));
/* 有効型: なし */
*(int *)p = 42;
/* 有効型: int */
*(float *)p = 3.14f;
/* 有効型: float(上書き) */
memcpy による有効型のコピー
int x = 42;
float f;
memcpy(&f, &x, sizeof(f)); /* 有効型はコピー元(int)になる? */
/* 実際には char 経由なので安全 */
---
6.3 型パニング(Type Punning)
NG: ポインタキャスト
float f = 3.14f;
int i = *(int *)&f; /* 未定義動作 */
OK: union を使用
union {
float f;
int i;
} u;
u.f = 3.14f;
int i = u.i; /* OK(C99以降) */
OK: memcpy を使用
float f = 3.14f;
int i;
memcpy(&i, &f, sizeof(i)); /* OK */
---
6.4 コンパイラオプション
GCC/Clang
# 厳密なエイリアシングを有効(デフォルト、-O2以上)
gcc -fstrict-aliasing
# 厳密なエイリアシングを無効
gcc -fno-strict-aliasing
# 違反を警告
gcc -Wstrict-aliasing
無効化の是非
-fno-strict-aliasing は違反を「許可」するわけではなく、最適化を抑制するだけです。移植性のあるコードを書くべきです。
---
6.5 実践的なガイドライン
ルール1: 異なる型のポインタで同じメモリにアクセスしない
/* NG */
int *ip = ...;
float *fp = (float *)ip;
*fp = 3.14f;
/* OK */
union { int i; float f; } u;
u.f = 3.14f;
ルール2: バイト操作は unsigned char を使用
void *p = malloc(n);
unsigned char *bytes = (unsigned char *)p;
/* 各バイトへのアクセスはOK */
ルール3: 型パニングは union または memcpy
/* union */
union { float f; uint32_t u; } converter;
converter.f = value;
uint32_t bits = converter.u;
/* memcpy */
memcpy(&bits, &value, sizeof(bits));
---
6.6 この章のまとめ
許可されるアクセス
- 互換型
- 符号の異なる対応型
- 文字型(char, unsigned char, signed char)
- メンバに含む集成型
- union(C99以降)
- memcpy
型パニングの方法
---
確認問題
問題1
次のコードは安全ですか?int i = 42;
unsigned int u = *(unsigned int *)&i;
解答
安全。signed/unsigned の対応型でのアクセスは許可されている。
問題2
float のビットパターンを uint32_t で取得する安全な方法は?
解答
/* union */
union { float f; uint32_t u; } conv;
conv.f = value;
uint32_t bits = conv.u;
/* または memcpy */
uint32_t bits;
memcpy(&bits, &value, sizeof(bits));
---
次の章では、メモリモデルと順序付けについて学びます。