型の幅以上の左シフトを試してみる

http://journal.mycom.co.jp/articles/2004/04/02/code/

古い記事だけど、

 この「1」が32bitのint型にキャストされたため「1 << 63」の結果として「0x80000000」が返ってきてしまい、正しい結果が得られなかったそうだ。

というのが気になった。てっきりシフトしすぎて0になるのかと思った。C99 規格を確認してみると整数拡張後の型の幅以上のシフトの結果は未定義扱い。未定義とはいえ、どうなるのかちょっと試してみた。

int main(int argc, char** argv) {
    unsigned int r1, r2, r3, r4, n;
 
    r1 = 1;
    r1 = r1 << 63; // 左オペランドが変数
 
    r2 = 1;
    r2 = r2 << 31; // 複数回に分けてシフト
    r2 = r2 << 31;
    r2 = r2 << 1;
 
    r3 = 1 << 63; // 両オペランドがリテラル
 
    n = 63;
    r4 = 1 << n; // 右オペランドが変数
 
    printf("r1 = %x, r2 = %x, r3 = %x, r4 = %x\n", r1, r2, r3, r4);
 
    return 0;
}

この実行結果は、

 r1 = 80000000, r2 = 0, r3 = 0, r4 = 80000000

どれも記述的には 1 を 63bit 左シフトしているけど結果は異なる。gcc -S -O0 でアセンブリコードを見ると

となっていた。算術左シフト(SAL)と論理左シフト(SHL) は動作としてはまったく同じで、sall の場合はカウンタレジスタ(あるいはイミディエイト)は下位5bitのみ有効ということで上位切り捨ての結果、63 → 31 となり、1 << 31 の結果が 0x80000000 となっている。31 + 31 + 1 で分けてシフトすればすべて有効なので押し出されて 0 となる。