Соглашение о вызовах для функции, возвращающей структуру - PullRequest
12 голосов
/ 08 февраля 2011

Для следующего кода 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 не определяет соглашение о вызовах для функции, возвращающей структуру?!

EDIT:

Я добавил реализацию вызываемого объекта.И конечно же, вы можете видеть, что вызываемый 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 ;;;;;;;;;;;;;;

Ответы [ 2 ]

9 голосов
/ 24 апреля 2017

Почему вызывающая сторона в Linux32 делает эти дополнительные подпрограммы?

Причина заключается в использовании скрытого указателя ( именованная оптимизация возвращаемого значения ), введенногокомпилятор, для возврата структуры по значению.В SystemV's ABI , стр. 41, в разделе «Структуры, возвращающие функции или объединения» говорится:

Вызываемая функция должна удалить этот адрес из стека.перед возвратом.

Именно поэтому вы получаете ret $0x4 в конце test_callee5(), это для соответствия ABI.

Теперь о наличииsub $0x4, %esp сразу после каждого test_callee5() сайтов вызовов, это побочный эффект вышеприведенного правила в сочетании с оптимизированным кодом, сгенерированным компилятором C.Поскольку пространство стека локального хранилища полностью зарезервировано:

3:  sub    $0x38,%esp

, нет необходимости нажимать / выдвигать скрытый указатель, он просто записывается внизу предварительно зарезервированного пространства (на которое указываетesp), используя mov %eax,(%esp) в строках 9 и 17. Поскольку указатель стека не уменьшен, sub $0x4,%esp предназначен для того, чтобы свести на нет эффект ret $0x4 и сохранить указатель стека без изменений.

В Win32 (с использованием компилятора MSVC, я думаю) такого правила ABI нет, используется простое ret (как и ожидалось в cdecl), скрытый указатель помещается в стек в строках 7 и 11. Хотя эти слотыне освобождаются после вызовов, как оптимизация, но только перед выходом вызываемого, используя add esp,1Ch, освобождая слоты стека скрытых указателей (2 * 0x4 байта) и локальную структуру AStruct (0x14 байтов).

Разве cdecl не определяет соглашение о вызовах для функции, возвращающей структуру?!

К сожалению, это не так, это зависит от компиляторов C и операционных систем

0 голосов
/ 08 февраля 2011

Не существует единого соглашения о вызовах "cdecl".Он определяется компилятором и операционной системой.

Также, читая сборку. На самом деле я не уверен, что соглашение фактически отличается - в обоих случаях вызывающая сторона предоставляет буфер для вывода в качестве дополнительного аргумента.Просто gcc выбрал разные инструкции (второй дополнительный подпункт странный; оптимизирован ли этот код?).

...