Тонкости работы с vararg - C (СИ)

Узнай цену своей работы

Формулировка задачи:

Думается, многим будет полезно попробовать разобраться с одной проблемой. С одной стороны не наступить на эти грабли в будущем, с другой стороны попробовать докопаться до сути ошибки, которая с виду возникает на ровном месте
#include <stdio.h>
#include <stdarg.h>
 
void foo (va_list ap)
{
  int r;
 
  r = va_arg (ap, int);
  printf ("param1 = 0x%x\n", r);
  r = va_arg (ap, int);
  printf ("param2 = 0x%x\n", r);
  r = va_arg (ap, int);
  printf ("param3 = 0x%x\n", r);
}
 
void bar (int x, ...)
{
  va_list ap;
 
  va_start (ap, x);
  foo (ap);
  foo (ap);
  va_end (ap);
}
 
int main (void)
{
  bar (0x11111111, 0x22222222, 0x33333333, 0x44444444);
  return 0;
}
При работе на sparc'е этот код работает одинаково правильно как в режиме 32, так и в режиме 64. При работе на intel'е этот код работает правильно в режиме 32, но ошибочно в режиме 64
$ gcc -m32 t.c
$ ./a.out 
param1 = 0x22222222
param2 = 0x33333333
param3 = 0x44444444
param1 = 0x22222222
param2 = 0x33333333
param3 = 0x44444444

$ gcc -m64 t.c
$ ./a.out 
param1 = 0x22222222
param2 = 0x33333333
param3 = 0x44444444
param1 = 0x59407c60
param2 = 0x5941ade0
param3 = 0x0
Собственно, вопрос простой - почему так происходит? Или, другими словами, где ошибка в коде? Традиционная просьба прятать ответы под спойлер

Решение задачи: «Тонкости работы с vararg»

textual
Листинг программы
.LC0:
        .string "param1 = 0x%x\n"
.LC1:
        .string "param2 = 0x%x\n"
.LC2:
        .string "param3 = 0x%x\n"
foo(__va_list_tag*):
        push    rbp
        mov     rbp, rsp
        sub     rsp, 32
        mov     QWORD PTR [rbp-24], rdi
        mov     rax, QWORD PTR [rbp-24]
        mov     eax, DWORD PTR [rax]
        cmp     eax, 47
        ja      .L2
        mov     rax, QWORD PTR [rbp-24]
        mov     rdx, QWORD PTR [rax+16]
        mov     rax, QWORD PTR [rbp-24]
        mov     eax, DWORD PTR [rax]
        mov     eax, eax
        add     rax, rdx
        mov     rdx, QWORD PTR [rbp-24]
        mov     edx, DWORD PTR [rdx]
        lea     ecx, [rdx+8]
        mov     rdx, QWORD PTR [rbp-24]
        mov     DWORD PTR [rdx], ecx
        jmp     .L3
.L2:
        mov     rax, QWORD PTR [rbp-24]
        mov     rax, QWORD PTR [rax+8]
        lea     rcx, [rax+8]
        mov     rdx, QWORD PTR [rbp-24]
        mov     QWORD PTR [rdx+8], rcx
.L3:
        mov     eax, DWORD PTR [rax]
        mov     DWORD PTR [rbp-4], eax
        mov     eax, DWORD PTR [rbp-4]
        mov     esi, eax
        mov     edi, OFFSET FLAT:.LC0
        mov     eax, 0
        call    printf
        mov     rax, QWORD PTR [rbp-24]
        mov     eax, DWORD PTR [rax]
        cmp     eax, 47
        ja      .L4
        mov     rax, QWORD PTR [rbp-24]
        mov     rdx, QWORD PTR [rax+16]
        mov     rax, QWORD PTR [rbp-24]
        mov     eax, DWORD PTR [rax]
        mov     eax, eax
        add     rax, rdx
        mov     rdx, QWORD PTR [rbp-24]
        mov     edx, DWORD PTR [rdx]
        lea     ecx, [rdx+8]
        mov     rdx, QWORD PTR [rbp-24]
        mov     DWORD PTR [rdx], ecx
        jmp     .L5
.L4:
        mov     rax, QWORD PTR [rbp-24]
        mov     rax, QWORD PTR [rax+8]
        lea     rcx, [rax+8]
        mov     rdx, QWORD PTR [rbp-24]
        mov     QWORD PTR [rdx+8], rcx
.L5:
        mov     eax, DWORD PTR [rax]
        mov     DWORD PTR [rbp-4], eax
        mov     eax, DWORD PTR [rbp-4]
        mov     esi, eax
        mov     edi, OFFSET FLAT:.LC1
        mov     eax, 0
        call    printf
        mov     rax, QWORD PTR [rbp-24]
        mov     eax, DWORD PTR [rax]
        cmp     eax, 47
        ja      .L6
        mov     rax, QWORD PTR [rbp-24]
        mov     rdx, QWORD PTR [rax+16]
        mov     rax, QWORD PTR [rbp-24]
        mov     eax, DWORD PTR [rax]
        mov     eax, eax
        add     rax, rdx
        mov     rdx, QWORD PTR [rbp-24]
        mov     edx, DWORD PTR [rdx]
        lea     ecx, [rdx+8]
        mov     rdx, QWORD PTR [rbp-24]
        mov     DWORD PTR [rdx], ecx
        jmp     .L7
.L6:
        mov     rax, QWORD PTR [rbp-24]
        mov     rax, QWORD PTR [rax+8]
        lea     rcx, [rax+8]
        mov     rdx, QWORD PTR [rbp-24]
        mov     QWORD PTR [rdx+8], rcx
.L7:
        mov     eax, DWORD PTR [rax]
        mov     DWORD PTR [rbp-4], eax
        mov     eax, DWORD PTR [rbp-4]
        mov     esi, eax
        mov     edi, OFFSET FLAT:.LC2
        mov     eax, 0
        call    printf
        nop
        leave
        ret
bar(int, ...):
        push    rbp
        mov     rbp, rsp
        sub     rsp, 224
        mov     DWORD PTR [rbp-212], edi
        mov     QWORD PTR [rbp-168], rsi
        mov     QWORD PTR [rbp-160], rdx
        mov     QWORD PTR [rbp-152], rcx
        mov     QWORD PTR [rbp-144], r8
        mov     QWORD PTR [rbp-136], r9
        test    al, al
        je      .L10
        movaps  XMMWORD PTR [rbp-128], xmm0
        movaps  XMMWORD PTR [rbp-112], xmm1
        movaps  XMMWORD PTR [rbp-96], xmm2
        movaps  XMMWORD PTR [rbp-80], xmm3
        movaps  XMMWORD PTR [rbp-64], xmm4
        movaps  XMMWORD PTR [rbp-48], xmm5
        movaps  XMMWORD PTR [rbp-32], xmm6
        movaps  XMMWORD PTR [rbp-16], xmm7
.L10:
        mov     DWORD PTR [rbp-200], 8
        mov     DWORD PTR [rbp-196], 48
        lea     rax, [rbp+16]
        mov     QWORD PTR [rbp-192], rax
        lea     rax, [rbp-176]
        mov     QWORD PTR [rbp-184], rax
        lea     rax, [rbp-200]
        mov     rdi, rax
        call    foo(__va_list_tag*)
        lea     rax, [rbp-200]
        mov     rdi, rax
        call    foo(__va_list_tag*)
        nop
        leave
        ret
main:
        push    rbp
        mov     rbp, rsp
        mov     ecx, 1145324612
        mov     edx, 858993459
        mov     esi, 572662306
        mov     edi, 286331153
        mov     eax, 0
        call    bar(int, ...)
        mov     eax, 0
        pop     rbp
        ret

Объяснение кода листинга программы

  1. foo(__va_list_tag*):
    • Проверяет, является ли первый аргумент 47. Если это так, то аргументы 4 и 5 выводятся как 0x%x и 0x%x.
    • Если первый аргумент не равен 47, то второй аргумент выводится как 0x%x.
    • Затем происходит обход стека вызовов и вывод каждого аргумента в формате 0x%x.
    • В конце функция foo() вызывается с аргументами 4 и 5.
  2. bar(int, ...):
    • Проверяет, является ли первый аргумент 47. Если это так, то аргументы 4 и 5 выводятся как 0x%x и 0x%x.
    • Если первый аргумент не равен 47, то второй аргумент выводится как 0x%x.
    • Затем происходит обход стека вызовов и вывод каждого аргумента в формате 0x%x.
    • В конце функция bar() вызывается с аргументами 4 и 5.
  3. main():
    • Инициализирует стек вызовов с аргументами 1145324612, 858993459, 572662306 и 286331153.
    • Вызывает функцию bar() с этими аргументами.
    • Возвращает 0.

Оцени полезность:

7   голосов , оценка 3.857 из 5