やっぱりお腹が痛くなる

この 2[buf] みたいな書き方もできちゃうというのが記憶の片隅にあり、true と印字されるんだろうなと思ったので最初の選択肢をポチッとした。 言語仕様で決まっているのかどうだったかまでは覚えてないけれども、とりあえずコンパイラがどんな機械語を生成するのか確認してみた。

環境

コンパイル&実行

やはり true が印字される。

[yusuke@albirex foo]$ gcc -g foo.c -o foo
[yusuke@albirex foo]$ file ./foo
./foo: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=12cde68dd82759dc3f81ce929093e43480f3c6f7, not stripped
[yusuke@albirex foo]$ ./foo
true
[yusuke@albirex foo]$ 

disas main

やはり 2[buf]buf[2] のように処理されている。。。

(gdb) disas main
Dump of assembler code for function main:
   0x000000000040052d <+0>:   push   %rbp
   0x000000000040052e <+1>:   mov    %rsp,%rbp
   0x0000000000400531 <+4>:   sub    $0x10,%rsp                 スタックを16バイト伸長する
   0x0000000000400535 <+8>:   movl   $0x33323032,-0x10(%rbp)    ベースポインタから -0x10 のところから "2" "0" "2" "3"
   0x000000000040053c <+15>:  movb   $0x0,-0xc(%rbp)            nullターミネート "\0"
   0x0000000000400540 <+19>:  movzbl -0x10(%rbp),%eax           buf[0] の値をバイト幅で eax へ
   0x0000000000400544 <+23>:  mov    %al,-0x1(%rbp)             eax の下位のバイトを rbp-1 へ
   0x0000000000400547 <+26>:  movzbl -0xe(%rbp),%eax            2[buf] の値をバイト幅で eax へ
   0x000000000040054b <+30>:  mov    %al,-0x2(%rbp)             eax の下位のバイトを rbp -2 へ
   0x000000000040054e <+33>:  movzbl -0x1(%rbp),%eax            rbp-1に取っておいた値をバイト幅で eax へ
   0x0000000000400552 <+37>:  cmp    -0x2(%rbp),%al             eax の下位バイトと rbp-2 の値を比較
   0x0000000000400555 <+40>:  sete   %al                        ...以下省略...
   0x0000000000400558 <+43>:  mov    %al,-0x3(%rbp)
   0x000000000040055b <+46>:  cmpb   $0x0,-0x3(%rbp)
   0x000000000040055f <+50>:  je     0x400568 <main+59>
   0x0000000000400561 <+52>:  mov    $0x400610,%eax
   0x0000000000400566 <+57>:  jmp    0x40056d <main+64>
   0x0000000000400568 <+59>:  mov    $0x400615,%eax
   0x000000000040056d <+64>:  mov    %rax,%rdi
   0x0000000000400570 <+67>:  callq  0x400410 <puts@plt>
   0x0000000000400575 <+72>:  mov    $0x0,%eax
   0x000000000040057a <+77>:  leaveq 
   0x000000000040057b <+78>:  retq   
End of assembler dump.
(gdb) 

メモリ上の配置

分かりにくいけど、こんな感じ。

        +------ rbp-0x10。つまり buf[0]
        | +---- rbp-0xe。 つまり buf[2] であり 2[buf]
        | | +-- rbp-0xc。 つまり buf[4]
        | | |           +-- rbp
        | | |           |        
        v v v           v
--------+-+-+-----------+-------
        2023n         22          // n は \0。2, 0, 2, 3 は、数値ではなく char。
--------+-------------+++-------
        |             ^^
        +-- rsp       ||
                      |+-- buf[0] からのコピー。つまり char c1
                      +--- 2[buf] からのコピー。つまり char c2

gcc オプション

ちなみに、-g をつけない場合も、main の disas は同じだった。-g はデバッグ情報を持つセクションを用意するだけで生成するマシン語には影響しないと思っておいてよさそう。 -O1 だとこんな感じ。もはや true を印字するのみ。

(gdb) disas main
Dump of assembler code for function main:
   0x000000000040052d <+0>:   sub    $0x8,%rsp
   0x0000000000400531 <+4>:   mov    $0x4005e0,%edi
   0x0000000000400536 <+9>:   callq  0x400410 <puts@plt>
   0x000000000040053b <+14>:  mov    $0x0,%eax
   0x0000000000400540 <+19>:  add    $0x8,%rsp
   0x0000000000400544 <+23>:  retq   
End of assembler dump.
(gdb) 
(gdb) x/s $rdi
0x4005e0:   "true"
(gdb) 

そして、gcc がどんなアセンブラを作るのかだけみたいのなら、gdb など使わずとも gcc -S foo.c とやれば良い。数値が 10 進数で表記されている。

[yusuke@albirex foo]$ gcc -S foo.c
[yusuke@albirex foo]$ cat foo.s
    .file   "foo.c"
    .section    .rodata
.LC0:
    .string "true"
.LC1:
    .string "false"
    .text
    .globl  main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    subq    $16, %rsp
    movl    $858927154, -16(%rbp)
    movb    $0, -12(%rbp)
    movzbl  -16(%rbp), %eax
    movb    %al, -1(%rbp)
    movzbl  -14(%rbp), %eax
    movb    %al, -2(%rbp)
    movzbl  -1(%rbp), %eax
    cmpb    -2(%rbp), %al
    sete    %al
    movb    %al, -3(%rbp)
    cmpb    $0, -3(%rbp)
    je  .L2
    movl    $.LC0, %eax
    jmp .L3
.L2:
    movl    $.LC1, %eax
.L3:
    movq    %rax, %rdi
    call    puts
    movl    $0, %eax
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
    .ident  "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-44)"
    .section    .note.GNU-stack,"",@progbits
[yusuke@albirex foo]$