日別アーカイブ: 2014年1月3日

VisualStudio と C++11 、コンパイラの違いなど

Visual C++ Compiler Nov 2012 CTP では動いていたけど、
VisualStudio 2013 以降コンパイルできなくなったコード。

struct List {
    int var_a, var_b;
    List( int a, int b ) : var_a( a ), var_b( b )
    {
    }
};

class Tree {
public:
    template
    T* Alloc( A0... a0 )
    {
        return  new T( a0... );
    }
};

class Compiler {
    Tree    tree;
public:
    template
    T* Alloc( A0... a0 )
    {
        return  tree.Alloc( a0... );
    }
};

...

Compiler compiler;
compiler.Alloc( 1, 2 );
VS2012 + Nov 2012 CTP : OK
VS2013                : internal error
VS2013 + Nov 2013 CTP : internal error
gcc 4.8.1             : OK
clang 3.2             : OK

template を分解して単純化して回避。

struct T0 {
    T0()= default;
    T0( const T0& )= default;
    T0( int a ){};
};

↑ T0 は VS ではまだ POD 扱いにならないようです。

                 std::is_pod
-----------------------------------
VS2013                 false
VS2013 + Nov 2013 CTP  false
gcc 4.8.1 (Linux)      true
clang 3.2 (Linux)      true

POD かどうかで、値渡し時に関数の呼び出し方が変わります。
sizeof が 8byte 未満でも register に入らず参照戻しになります。

struct T1 {
    T1()= default;
    T1( const T1& )= default;
    int var;
};
struct T2 {
    T2()= default;
    T2( const T2& )= default;
    T2( int a ){};
    int var;
};

T1 pod_test1( T1 a )
{
    return  a;
}
T2 pod_test2( T2 a )
{
    return  a;
}

void pod_test()
{
   T1 a1;
   T1 ret1= pod_test1( a1 );
   T2 a2;
   T2 ret2= pod_test2( a2 );
}
// Windows x64
// T1
    xor ecx, ecx
    call    ?pod_test1@@YA?AUT1@@U1@@Z      ; pod_test1
// T2
    lea rcx, QWORD PTR $T2[rsp]
    xor edx, edx
    call    ?pod_test2@@YA?AUT2@@U1@@Z      ; pod_test2

Windows x64 は fastcall 相当なので引数はレジスタ ecx, edx, r8, r9 に入ります。
上の T1 ではそのまま引数が ecx に入っています。

↑ T2 の場合戻り値を受け取るために、呼び出し側が領域を確保して rcx で渡しています。
実際の引数は edx です。

// Windows x64
// T1
?pod_test1@@YA?AUT1@@U1@@Z PROC             ; pod_test1, COMDAT
    mov eax, ecx
    ret 0

// T2
?pod_test2@@YA?AUT2@@U1@@Z PROC             ; pod_test2, COMDAT
    mov DWORD PTR [rcx], edx
    mov rax, rcx
    ret 0

↑ T1 では呼ばれた側は引数を戻り値 rax にコピーしているだけ。
↑ T2 は rcx のバッファに結果を格納しつつ rax にもアドレスを返しています。

gcc 4.8/clang 3.2 ではどちらも POD 扱いなので T1/T2 共に同じコードとなっています。
Linux では呼び出し規約 (calling convention) が異なるのでレジスタは別です。
(rdi, rsi, rdx, rcx, r8, r9 の順)

Function Calling convention

// Linux x86_x64
// T1
    xorl    %edi, %edi
    callq   _Z9pod_test12T1
// T2
    xorl    %edi, %edi
    callq   _Z9pod_test22T2
// Linux x86_x64
// T1
_Z9pod_test12T1:                        # @_Z9pod_test12T1
    movl    %edi, %eax
    ret
// T2
_Z9pod_test22T2:                        # @_Z9pod_test22T2
    movl    %edi, %eax
    ret

下記は gcc 以外はコンパイルが通ります。

template
void CallScript_UIEvent( A0... a0 )
{
    slice::Add( "Move",
        [=]( slice::SliceBase* slice ){
            CallScript_Direct( a0... );
            slice->Delete();
        }
    );
}
VS2012 + Nov 2012 CTP : OK
VS2013                : OK
VS2013 + Nov 2013 CTP : OK
gcc 4.8.1 (Linux)     : error
clang 3.2 (Linux)     : OK

template 引数が可変長でなければコンパイル通ります。

関連エントリ
C++11 Lambda function ラムダ関数
C++11 Variadic Templates 可変長引数のテンプレート
スレッド同期命令の比較 C++11 とコンパイラ
C++11 Rvalue Reference 右辺値参照