C++ コンパイラにおける値渡しの最適化

 プリミティブ型でなくとも小さな構造体 (or 共用体) ならレジスタ渡しになる、ってのは C で最適化されたコードを書く際に、しばしば使われるテクニック。でも C++ だと、コピーコンストラクタやデストラクタがある場合はこの最適化が不可能。他にも言語仕様的に細かな条件はあるのかな。ともかくコンパイラの実装でこの可否判断はどうなっているのか、と SUN のドキュメントを読んでいたら、とても頑張っていて驚いた。

In compatibility mode (-compat[=4]), a class is passed indirectly if it has any one of the following:

  • A user-defined constructor
  • A virtual function
  • A virtual base class
  • A base that is passed indirectly
  • A non-static data member that is passed indirectly

Otherwise, the class is passed directly.

In standard mode (the default mode), a class is passed indirectly if it has any one of the following:

  • A user-defined copy constructor
  • A user-defined destructor
  • A base that is passed indirectly
  • A non-static data member that is passed indirectly

Otherwise, the class is passed directly.

http://docs.sun.com/app/docs/doc/819-5267/6n7c46doi?l=ja&a=view#indexterm-311

さすがはレジスタ渡し命な SPARC アーキテクチャ向けのコンパイラ。仮想関数があるのにデストラクタがないなんてことはなかなかあり得ないけど、そういう場合の最適化までしてる。

 で、GCC の場合を試してみた (gcc 4.0.1; i386) けど、こんな感じだった。

call    __Z15createT_C_stylei
movl    %eax, 4(%esp)
leal    LC1-"L00000000002$pb"(%ebx), %esi
movl    %esi, (%esp)
call    L_printf$stub
(snip)
call    __Z12createT_dtori
subl    $4, %esp
movl    -36(%ebp), %eax
movl    %eax, 4(%esp)
movl    %esi, (%esp)
call    L_printf$stub
(snip)
call    __Z12createT_mfuni
movl    %eax, 4(%esp)
movl    %esi, (%esp)
call    L_printf$stub
(snip)
call    __Z12createT_vfuni
subl    $4, %esp
movl    -32(%ebp), %eax
movl    %eax, 4(%esp)
movl    %esi, (%esp)
call    L_printf$stub

 デストラクタや仮想関数がある場合は、常に参照渡しになるらしい。継承まわりは未確認。

 ちなみに使用した検証コードは以下のとおり。最適化に興味がある方は、どうぞご利用ください。

#include <stdio.h>

struct T_C_style {
  int v;
};

struct T_dtor {
  int v;
  ~T_dtor() {}
};

struct T_mfun {
  int v;
  void foo();
};

struct T_vfun {
  int v;
  virtual void foo() {}
};

#define CREATE(T) T create##T(int v) { T t; t.v = v; return t; }
CREATE(T_C_style);
CREATE(T_dtor);
CREATE(T_mfun);
CREATE(T_vfun);

int main(int argc, char **argv)
{
  int v = 0;
  sscanf(argv[1], "%d", &v);
  
#define TEST(T) { T t(create##T(v)); printf("%d\n", t.v); }
  
  TEST(T_C_style);
  TEST(T_dtor);
  TEST(T_mfun);
  TEST(T_vfun);
  
  return 0;
}

3月21日追記: 上のコードは、実際の関数呼び出しにおける最適化手法を確認する目的で書かれています。そのため、インライン展開されないよう (テンプレートではなく) #define を用いて関数を定義しています。