printf: как объяснить искаженный результат - PullRequest
1 голос
/ 08 сентября 2010
#include <stdio.h>

int main(void)
{
        double resd = 0.000116;
        long long resi = 0;

        printf("%lld %f %lld %f\n", resd, resd, resi, resi);
        return 0;
}

дает (Linux, gcc, x64)

0 0.000116 0 0.000116
             ^^^^^^^^ odd, since the memory for resi is zeroed

На самом деле, скомпилированный с g ++, он дает случайные результаты вместо второго 0.

Я понимаю, что передал недопустимые спецификаторы printf и что это вызывает неопределенное неопределенное поведение, но мне интересно, почему происходит это конкретное повреждение, поскольку long long и double имеют одинаковый размер.

Ответы [ 3 ]

10 голосов
/ 08 сентября 2010

Это потому, что в соответствии с соглашениями о вызовах x86_64 C на вашей платформе первые два аргумента с плавающей точкой передаются в xmm0 и xmm1, а первые два целочисленных аргумента передаются в GPR (rsi иrdx, если вы работаете в Linux или OS X), независимо от того, в каком порядке они отображаются.

Вы сбиты с толку, поскольку ожидаете, что параметры передаются в память;это не так.

5 голосов
/ 08 сентября 2010

Я получаю те же результаты, что и вы на моей машине (Mac OS X, то есть AMD / Linux ABI). Параметры с плавающей запятой передаются в XMM-регистрах, а целочисленные параметры - в целочисленных. Когда printf захватывает их, используя va_arg, он извлекает данные из XMM, когда видит формат %f, и из других регистров, когда видит %lld. Вот разборка вашей программы скомпилированной (-O0) на моей машине:

 1 _main:
 2   pushq   %rbp
 3   movq    %rsp,%rbp
 4   subq    $0x20,%rsp
 5   movq    $0x3f1e68a0d349be90,%rax
 6   move    %rax,0xf8(%rbp)
 7   movq    $0x00000000,0xf0(%rbp)
 8   movq    0xf0(%rbp),%rdx
 9   movq    0xf0(%rbp),%rsi
10   movsd   0xf8(%rbp),%xmm0
11   movq    0xf8(%rbp),%rax
12   movapd  %xmm0,%xmm1
13   movq    %rax,0xe8(%rbp)
14   movsd   0xe8(%rbp),%xmm0
15   lea     0x0000001d(%rip),%rdi
16   movl    $0x00000002,%eax
17   callq   0x100000f22    ; symbol stub for: _printf
18   movl    $0x00000000,%eax
19   leave
20   ret

Там вы можете увидеть, что происходит - строка формата передается в %rdi, затем ваши параметры передаются (по порядку) в: %xmm0, %xmm1, %rsi и %rdx. Когда printf получает их, он выталкивает их в другом порядке (порядке, указанном в вашей строке формата). Это означает, что они появляются: %rsi, %xmm0, %rdx, %xmm1, давая результаты, которые вы видите. 2 в %eax указывает количество переданных аргументов с плавающей запятой.

Edit:

Вот оптимизированная версия - в этом случае более короткий код может быть легче для понимания. Объяснение то же, что и выше, но с чуть меньшим шумом. Значение с плавающей запятой загружается movsd в строке 4.

 1 _main:
 2    pushq   %rbp
 3    movq    %rsp,%rbp
 4    movsd   0x00000038(%rip),%xmm0
 5    xorl    %edx,%edx
 6    xorl    %esi,%esi
 7    movaps  %xmm0,%xmm1
 8    leaq    0x00000018(%rip),%rdi
 9    movb    $0x02,%al
10    callq   0x100000f18   ; symbol stub for: _printf
11    xorl    %eax,%eax
12    leave
13    ret
2 голосов
/ 08 сентября 2010
  • Первое число должно быть высоким значением, поскольку вы передаете двойное число как целое число. Должно быть% f.
  • Что с периодом после «0» в «resi»? Это превратит его в двойное, поэтому вы пытаетесь загрузить двойное в целое число. Это должно дать вам предупреждение компилятора.
  • Некоторые реализации могут основываться на регистрах, поэтому, поскольку вы путаете типы аргументов, это запутывается.

На какой платформе вы компилируете? Окна

Вы смотрели на разборку, чтобы увидеть, что она на самом деле помещает в стек? Он даже помещает их в стек или использует регистры?

...