[C] Cのstrict aliasingについて

2016年6月13日月曜日

C

[C] Cで10進小数を使う方法 でdecNumberを紹介したけど、そのドキュメントの中にこんな記述があった。

http://speleotrove.com/decimal/dnmods.html
The modules all conform to some general rules:
They are reentrant (they have no static variables and may safely be used in multi-threaded applications), and use only aligned integers and strict aliasing.

リエントラントやアラインメントはいいとしてもstrict aliasingってなんだろう?

Cでは同じアドレスを指すのに異なる変数が使われるなんてことはよくある。それがエイリアシングだ。
じゃあ、strict aliasing、エイリアシングに厳格ってどういうことだろうか?

C89の規格によるとstrict aliasing rulesは以下となっている。[1]

オブジェクトに格納された値に対するアクセスは,次のうちのいずれか1つの型を持つ左辺値によらなければならない。
  • オブジェクトの有効型と適合する型
  • オブジェクトの有効型と適合する型の修飾版
  • オブジェクトの有効型に対応する符号付き型または符号無し型
  • オブジェクトの有効型の修飾版に対応する符号付き型または符号無し型
  • メンバの中に上に列挙した型の1つを含む集成体型または共用体型(再帰的に包含されている部分集成体または含まれる共用体のメンバを含む)
  • 文字型

つまり、このルールに従わない場合の動作は未定義ということになる。
なんでこんなルールがあるかというと、コンパイラの最適化をしやすくする為のようだ。

例えば、以下のコードを最適化ありとなしでコンパイルすると結果が変わってきてしまう。

aliasing.c
int init(int *a, short *b) {
    *a = 0;
    *b = 1;

    return *a;
}

int main(void) {
    int a;
    short *b = (short*)&a;

    a = init(&a, b); // 異なる型への参照を無理やり渡す!

    return a;
}
$ clang -Wall -o aliasing aliasing.c
$ ./aliasing;  echo $?
1
$ clang -Wall -O2 -o aliasing aliasing.c
$ ./aliasing;  echo $?
0

この場合、-O2オプションをつけるとstrict aliasingを満たしているとみなされ、init関数のaとbの変数が同じアドレスを指していることはないという前提で最適化される。
そのため、無条件に戻り値は0となってしまう。

これはコンパイラの最適化によって挙動が変わってしまう例だけれど、プログラマがエイリアシングのルールを守っていないことによるものなんだね。

[1]参考: EXP39-C. 適合しない型のポインタを使って変数にアクセスしない