アセンブラからみた効率のよい引数の型

C言語の関数の引数ですが、どんな型にすると効率が良いのでしょう。
ALレジスタを見ると、8bitアクセスなので、一見効率がよさそうに見えます。

+-------+-------+-------+-------+-------+-------+-------+-------+
|                           RAX(64)                             |
+-------+-------+-------+-------+-------+-------+-------+-------+
|                               |            EAX(32)            |
+-------+-------+-------+-------+-------+-------+-------+-------+
|                                               |     AX(16)    |
+-------+-------+-------+-------+-------+-------+-------+-------+
|                                               | AL(8) | AL(8) |
+-------+-------+-------+-------+-------+-------+-------+-------+

引数なし、8bit、16bit、32bit、64bitの型の引数の関数を呼び出しした時に、アセンブラがどのように変化するか確認してみます。

引数なしの場合。

takk@deb9:~/tmp$ cat -n t.c
     1  void func1()
     2  {
     3
     4  }
     5
     6  int main()
     7  {
     8          func1();
     9  }
takk@deb9:~/tmp$
takk@deb9:~/tmp$ gcc t.c
takk@deb9:~/tmp$ objdump -d a.out

引数なしだと、アセンブラはとてもスッキリしています。
関数に何も書かないと、nopが入るのですね。

0000000000000660 <func1>:
 660:   55                      push   %rbp
 661:   48 89 e5                mov    %rsp,%rbp
 664:   90                      nop
 665:   5d                      pop    %rbp
 666:   c3                      retq

0000000000000667 <main>:
 667:   55                      push   %rbp
 668:   48 89 e5                mov    %rsp,%rbp
 66b:   b8 00 00 00 00          mov    $0x0,%eax
 670:   e8 eb ff ff ff          callq  660 <func1>
 675:   b8 00 00 00 00          mov    $0x0,%eax
 67a:   5d                      pop    %rbp
 67b:   c3                      retq
 67c:   0f 1f 40 00             nopl   0x0(%rax)

次は8bitの引数の場合。char型を使ってみます。

takk@deb9:~/tmp$ cat -n t.c
     1  void func1(char aaa)
     2  {
     3
     4  }
     5
     6  int main()
     7  {
     8          func1(100);
     9  }
takk@deb9:~/tmp$

アセンブラです。8bitなので、ALレジスタが使用されています(666行目)

0000000000000660 <func1>:
 660:   55                      push   %rbp
 661:   48 89 e5                mov    %rsp,%rbp
 664:   89 f8                   mov    %edi,%eax
 666:   88 45 fc                mov    %al,-0x4(%rbp)
 669:   90                      nop
 66a:   5d                      pop    %rbp
 66b:   c3                      retq

000000000000066c <main>:
 66c:   55                      push   %rbp
 66d:   48 89 e5                mov    %rsp,%rbp
 670:   bf 64 00 00 00          mov    $0x64,%edi
 675:   e8 e6 ff ff ff          callq  660 <func1>
 67a:   b8 00 00 00 00          mov    $0x0,%eax
 67f:   5d                      pop    %rbp
 680:   c3                      retq
 681:   66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)
 688:   00 00 00
 68b:   0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)

呼び出し側はなんだか複雑ですね。

次は16bit。shortの引数にします。

takk@deb9:~/tmp$ cat -n t.c
     1  void func1(short aaa)
     2  {
     3
     4  }
     5
     6  int main()
     7  {
     8          func1(100);
     9  }
takk@deb9:~/tmp$

ALレジスタがAXに変わっただけのようです。

0000000000000660 <func1>:
 660:   55                      push   %rbp
 661:   48 89 e5                mov    %rsp,%rbp
 664:   89 f8                   mov    %edi,%eax
 666:   66 89 45 fc             mov    %ax,-0x4(%rbp)
 66a:   90                      nop
 66b:   5d                      pop    %rbp
 66c:   c3                      retq

000000000000066d <main>:
 66d:   55                      push   %rbp
 66e:   48 89 e5                mov    %rsp,%rbp
 671:   bf 64 00 00 00          mov    $0x64,%edi
 676:   e8 e5 ff ff ff          callq  660 <func1>
 67b:   b8 00 00 00 00          mov    $0x0,%eax
 680:   5d                      pop    %rbp
 681:   c3                      retq
 682:   66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)
 689:   00 00 00
 68c:   0f 1f 40 00             nopl   0x0(%rax)

次は、32bit。longを使います。

takk@deb9:~/tmp$ cat -n t.c
     1  void func1(long aaa)
     2  {
     3
     4  }
     5
     6  int main()
     7  {
     8          func1(100);
     9  }
takk@deb9:~/tmp$

なんと! func1のコードが短くなっています。

0000000000000660 <func1>:
 660:   55                      push   %rbp
 661:   48 89 e5                mov    %rsp,%rbp
 664:   48 89 7d f8             mov    %rdi,-0x8(%rbp)
 668:   90                      nop
 669:   5d                      pop    %rbp
 66a:   c3                      retq

000000000000066b <main>:
 66b:   55                      push   %rbp
 66c:   48 89 e5                mov    %rsp,%rbp
 66f:   bf 64 00 00 00          mov    $0x64,%edi
 674:   e8 e7 ff ff ff          callq  660 <func1>
 679:   b8 00 00 00 00          mov    $0x0,%eax
 67e:   5d                      pop    %rbp
 67f:   c3                      retq

mainも短くなっています。

次は、64bit。long longでもよいのですが、分かりにくいのでint64_tにしました。

takk@deb9:~/tmp$ cat -n t.c
     1  #include <stdint.h>
     2  void func1(int64_t aaa)
     3  {
     4
     5  }
     6
     7  int main()
     8  {
     9          func1(100);
    10  }
takk@deb9:~/tmp$

32bitの時と同じコードサイズです。レジスタはrdiに変わってます。

0000000000000660 <func1>:
 660:   55                      push   %rbp
 661:   48 89 e5                mov    %rsp,%rbp
 664:   48 89 7d f8             mov    %rdi,-0x8(%rbp)
 668:   90                      nop
 669:   5d                      pop    %rbp
 66a:   c3                      retq

000000000000066b <main>:
 66b:   55                      push   %rbp
 66c:   48 89 e5                mov    %rsp,%rbp
 66f:   bf 64 00 00 00          mov    $0x64,%edi
 674:   e8 e7 ff ff ff          callq  660 <func1>
 679:   b8 00 00 00 00          mov    $0x0,%eax
 67e:   5d                      pop    %rbp
 67f:   c3                      retq

32bitも64bitもアセンブラのコードサイズは同じですね。
しかし、8bit/16bitと比べると数バイト短くなります。
引数の型は、32bit以上にすると、効率がよさそうです。