13

次のCコードの場合:

struct _AStruct {
    int a;
    int b;
    float c;
    float d;
    int e;
};

typedef struct _AStruct AStruct;

AStruct test_callee5();
void test_caller5();

void test_caller5() {
    AStruct g = test_callee5();
    AStruct h = test_callee5();    
}

Win32の場合、次のように分解されます。

_test_caller5:
  00000000: lea         eax,[esp-14h]
  00000004: sub         esp,14h
  00000007: push        eax
  00000008: call        _test_callee5
  0000000D: lea         ecx,[esp+4]
  00000011: push        ecx
  00000012: call        _test_callee5
  00000017: add         esp,1Ch
  0000001A: ret

Linux32の場合:

00000000 <test_caller5>:
   0:  push   %ebp
   1:  mov    %esp,%ebp
   3:  sub    $0x38,%esp
   6:  lea    0xffffffec(%ebp),%eax
   9:  mov    %eax,(%esp)
   c:  call   d <test_caller5+0xd>
  11:  sub    $0x4,%esp  ;;;;;;;;;; Note this extra sub ;;;;;;;;;;;;
  14:  lea    0xffffffd8(%ebp),%eax
  17:  mov    %eax,(%esp)
  1a:  call   1b <test_caller5+0x1b>
  1f:  sub    $0x4,%esp   ;;;;;;;;;; Note this extra sub ;;;;;;;;;;;;
  22:  leave
  23:  ret

呼び出し後の発信者の動作の違いを理解しようとしています。Linux32の呼び出し元がこれらの追加の潜水艦を行うのはなぜですか?

私は、両方のターゲットがcdecl呼び出し規約に従うと想定します。cdeclは、構造体を返す関数の呼び出し規約を定義していませんか?!

編集:

呼び出し先の実装を追加しました。そして確かに、Linux32の呼び出し先は引数をポップしますが、Win32の呼び出し先はそうではないことがわかります。

AStruct test_callee5()
{
    AStruct S={0};
    return S;
}

Win32の分解:

test_callee5:
  00000000: mov         eax,dword ptr [esp+4]
  00000004: xor         ecx,ecx
  00000006: mov         dword ptr [eax],0
  0000000C: mov         dword ptr [eax+4],ecx
  0000000F: mov         dword ptr [eax+8],ecx
  00000012: mov         dword ptr [eax+0Ch],ecx
  00000015: mov         dword ptr [eax+10h],ecx
  00000018: ret

Linux32の分解:

00000000 <test_callee5>:
   0:   push   %ebp
   1:   mov    %esp,%ebp
   3:   sub    $0x20,%esp
   6:   mov    0x8(%ebp),%edx
   9:   movl   $0x0,0xffffffec(%ebp)
  10:   movl   $0x0,0xfffffff0(%ebp)
  17:   movl   $0x0,0xfffffff4(%ebp)
  1e:   movl   $0x0,0xfffffff8(%ebp)
  25:   movl   $0x0,0xfffffffc(%ebp)
  2c:   mov    0xffffffec(%ebp),%eax
  2f:   mov    %eax,(%edx)
  31:   mov    0xfffffff0(%ebp),%eax
  34:   mov    %eax,0x4(%edx)
  37:   mov    0xfffffff4(%ebp),%eax
  3a:   mov    %eax,0x8(%edx)
  3d:   mov    0xfffffff8(%ebp),%eax
  40:   mov    %eax,0xc(%edx)
  43:   mov    0xfffffffc(%ebp),%eax
  46:   mov    %eax,0x10(%edx)
  49:   mov    %edx,%eax
  4b:   leave
  4c:   ret    $0x4  ;;;;;;;;;;;;;; Note this ;;;;;;;;;;;;;;
4

2 に答える 2

9

Linux32の呼び出し元がこれらの追加の潜水艦を行うのはなぜですか?

その理由は、構造体を値で返すために、コンパイラーによって挿入された非表示のポインター(戻り値の最適化という名前)を使用するためです。SystemVのABI、41ページ、「関数を返す構造体またはユニオン」に関するセクションでは、次のように述べています。

呼び出された関数は、戻る前にこのアドレスをスタックから削除する必要があります。

そのためret $0x4、最後にを取得しますtest_callee5()。これは、ABIに準拠するためです。

sub $0x4, %esp各呼び出しサイトの直後に存在することについてtest_callee5()は、上記のルールの副作用であり、Cコンパイラによって生成された最適化されたコードと組み合わされています。ローカルストレージスタックスペースは完全に事前予約されているため、次のようになります。

3:  sub    $0x38,%esp

非表示のポインタをプッシュ/ポップする必要はありません。9行目と17行目espを使用して、事前に予約されたスペース(で示される)の下部に書き込まれます。スタックポインタはデクリメントされないため、否定する必要があります。の効果、およびスタックポインタを変更しないでください。mov %eax,(%esp)sub $0x4,%espret $0x4

Win32(私が推測するMSVCコンパイラを使用)では、そのようなABIルールはなく、単純なretものが使用され(cdeclで期待されるように)、隠しポインタが7行目と11行目でスタックにプッシュされます。最適化としての呼び出し。ただし、呼び出し先が終了する前にのみ、を使用してadd esp,1Ch、非表示のポインタースタックスロット(2 * 0x4バイト)とローカルAStruct構造体(0x14バイト)を解放します。

cdeclは、構造体を返す関数の呼び出し規約を定義していませんか?!

残念ながら、そうではありません。Cコンパイラやオペレーティングシステムによって異なります。

于 2017-04-24T07:05:16.797 に答える
1

単一の「cdecl」呼び出し規約はありません。これは、コンパイラとオペレーティングシステムによって定義されます。

また、アセンブリを読んで、規則が実際に異なるかどうかは実際にはわかりません。どちらの場合も、呼び出し元は追加の引数として出力用のバッファーを提供しています。gccが別の命令を選択しただけです(2番目の余分なサブは奇妙です;そのコードは最適化されていますか?)。

于 2011-02-08T09:21:15.517 に答える