てがみ: qatacri at protonmail.com | 統計 | 2022

202228200

uint32_t foo() {
    uint32_t n;
    *reinterpret_cast<float*>(&n) = 0.0;
    return n;
}

のような aliasing rule を破るコードが、実際に最適化で壊れる場面に遭遇した。

(翻訳)C/C++のStrict Aliasingを理解する または - どうして#$@##@^%コンパイラは僕がしたい事をさせてくれないの! - yohhoyの日記
[C++]type punningとオブジェクト生存期間 - 地面を見下ろす少年の足蹴にされる私

考えていると memcpy が合法な理由が分からなくなってきた。 void* にキャストしてその参照先のメモリを書き換えるのは許されるの? と。

勘違いしそうになったけれど、 aliasing rule はべつにコード上のポインタのキャストを追っていくルールではない。ポインタが途中どんな変な型にキャストされようが問題なくて、最終的に同じメモリアドレスが違う型で読み書きされなければいい…はず。

void write_float(void* p) {
    *reinterpret_cast<float*>(p) = 0.0;
}

void write_int(void* p) {
    *reinterpret_cast<uint32_t*>(p) = 0;
}

void write_chars(void* p) {
    char* c = reinterpret_cast<char*>(p);
    c[0] = c[1] = c[2] = c[3] = 0;
}

uint32_t foo() {
    uint32_t n;
    write_int(&n); // legal
    write_float(&n); // UB
    write_chars(&n); // 例外的に legal
    return n;
}

インライン化などの関数間最適化が行われないかぎり、関数の処理内容はブラックボックスである。また、あるポインタが違う型の変数を参照している可能性は常にある。つまり aliasing rule があっても、オプティマイザは関数コールでかなり広い範囲の変数が書きかわる可能性を考慮しなければいけない。本当に??