2015-05-04 3 views
0

У меня есть программа goo.cПонимание неожиданного результата благодаря непревзойденному прототипу (C89)

void foo(double); 

#include <stdio.h> 
void foo(int x){ 
    printf ("in foo.c:: x= %d\n",x); 
} 

, который вызывается foo.c

int main(){ 
    double x=3.0; 
    foo(x); 
} 

я скомпилировать и запустить

gcc foo.c goo.c 
./a.out 

Угадайте, что? Я получаю результат «x = 1». Тогда я считаю, что подпись «foo» должна была быть void foo(int). По-видимому, мое двойное значение ввода 3.0 должно быть опущено до int. Но, если я пытаюсь увидеть значение (межд) 3.0 с тестовой программой:

int main(){ 
    double x=3.0; 
    printf ("%d", ((int) x)); 
} 

я получаю 3 в качестве выходного сигнала, что делает ранее `х = 1' еще более трудно понять. Есть идеи? Для получения информации, мой gcc запускается с использованием стандарта ANSI C. Благодарю.

[EDIT] Если я использую GCC -S как предложено СП1,

я goo.s

.file "goo.c" 
    .text 
    .globl main 
    .type main, @function 
main: 
.LFB0: 
    .cfi_startproc 
    pushq %rbp 
    .cfi_def_cfa_offset 16 
    .cfi_offset 6, -16 
    movq %rsp, %rbp 
    .cfi_def_cfa_register 6 
    subq $32, %rsp 
    movabsq $4613937818241073152, %rax 
    movq %rax, -8(%rbp) 
    movq -8(%rbp), %rax 
    movq %rax, -24(%rbp) 
    movsd -24(%rbp), %xmm0 
    call foo 
    leave 
    .cfi_def_cfa 7, 8 
    ret 
    .cfi_endproc 
.LFE0: 
    .size main, .-main 
    .ident "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2" 
    .section .note.GNU-stack,"",@progbits 

и foo.s

.file "foo.c" 
    .section .rodata 
.LC0: 
    .string "in foo.c:: x= %d\n" 
    .text 
    .globl foo 
    .type foo, @function 
foo: 
.LFB0: 
    .cfi_startproc 
    pushq %rbp 
    .cfi_def_cfa_offset 16 
    .cfi_offset 6, -16 
    movq %rsp, %rbp 
    .cfi_def_cfa_register 6 
    subq $16, %rsp 
    movl %edi, -4(%rbp) 
    movl -4(%rbp), %eax 
    movl %eax, %esi 
    movl $.LC0, %edi 
    movl $0, %eax 
    call printf 
    leave 
    .cfi_def_cfa 7, 8 
    ret 
    .cfi_endproc 
.LFE0: 
    .size foo, .-foo 
    .ident "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2" 
    .section .note.GNU-stack,"",@progbits 

Тот, кто знает, как read Assembly может помочь выяснить исходную проблему?

+1

На какой машине вы работаете? Скорее всего, произошло то, что двойники передаются функциям с использованием регистров с плавающей запятой (в отличие от регистров общего назначения, которые используются для целых чисел). Функция пытается прочитать из целочисленного регистра, который не инициализирован и просто имеет место 1. Вы можете точно видеть, что происходит, если вы используете '-S' и смотрите на код сборки для ваших функций. – JS1

ответ

2

Понимание причин, по которым вы получаете '1', требует немного ASM и x86-64 ABI знания. Прежде всего, goo.c и foo.c - это две отдельные компиляции единиц. Единственное, что foo.c знает о функции foo, это поддельный прототип.

Поддельный прототип выглядит следующим образом: void foo(double);. Это функция , которая принимает только один двойной аргумент. АБИ x86-64 указывает, что удваивает пропускание через регистры xmm (Точная фраза : «Если класс является SSE, используется следующий доступный векторный регистр, регистры берутся в порядке от% xmm0 до% ... xmm7 '

это означает, что, когда компилятор устанавливает аргументы для вызова функции foo(), он собирается передать аргумент через %xmm0 в упрощенном ассемблере, что случается:

mov 3.0, %xmm0 
call foo 

сейчас , foo(), на нем si де, считает, что он собирается получить int. x86-64 ABI говорит: «Если класс INTEGER, используется следующий доступный регистр последовательности% rdi,% rsi,% rdx,% rcx,% r8 и% r9. '. Первый аргумент должен быть передан через %rdi. Это означает, что foo() будет делать что-то вроде:

mov %rdi, %rsi 
mov 0xabcd, %rdi // 0xabcd being the address of the "%d" string 
call printf 

Итак, вы собираетесь закончить печать все, что было в %rsi, а не %xmm0.

Но почему 1? Вы получите представление о том, выполнив следующие команды: ./a.out a ./a.out a b ./a.out a b c

Посмотреть образец? Вернемся к упрощенному сборки:

main: 
    mov 3.0, %xmm0 
    call foo 
    ret 

foo: 
    mov %rdi, %rsi 
    mov 0xabcd, %rdi // 0xabcd being the address of the "%d" string 
    call printf 
    ret 

Как вы можете видеть, ничего не устанавливая %rdi, пока он не достигнет foo(), , где он передается на Printf. Что означает, что 1 было отправлено на номер main . Теперь в вопросе main приведено прототип : int main(). Но у компилятора на самом деле настройка функции на есть следующий прототип: int main (int argc, char *argv[], char *envp[]). Первый аргумент, сохраненный в %rdi, фактически равен argc. Вот почему программа печатала 1.

+0

Ничего себе! Отличное объяснение. Большое спасибо за подробный, информативный ответ. Я только что вставил файлы .s для вашей справки. – zell

+0

Мое удовольствие. Вы можете наслаждаться этой презентацией. Он охватывает аналогичные проблемы: http://www.slideshare.net/olvemaudal/deep-c –

Смежные вопросы