理解由于原型不匹配导致的意外结果(C89)

时间:2015-05-04 00:26:35

标签: c prototype c89

我有一个程序goo.c

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。但是,如果我尝试用测试程序看到(int)3.0的值:

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

我得到3作为输出,这使得先前的“x = 1”更难以理解。任何的想法?有关信息,我的gcc使用ANSI C标准运行。谢谢。

[编辑]如果我按照JS1的建议使用gcc -S,

我得到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

任何知道如何阅读汇编的人都可以帮助找出源问题吗?

1 个答案:

答案 0 :(得分:2)

了解为什么得到'1'需要一点ASM和x86-64 ABI 知识。首先,goo.cfoo.c是两个单独的编译 单位。 foo.c了解foo函数的唯一事情是 伪造的原型。

虚假原型如下:void foo(double);。这是一个功能 只需要一个双参数。 x86-64 ABI要求这样做 双打通过xmm寄存器(确切的措辞 是'如果类是SSE,则使用下一个可用的向量寄存器, 寄存器按从%xmm0到%xmm7的顺序获取。'。

这意味着当编译器设置参数来调用 foo()函数,它将通过%xmm0传递参数。在 简化asm会发生什么:

mov 3.0, %xmm0
call foo

现在,foo()就此而言,相信它会收到一个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()之前,没有任何内容正在设置1, 它传递给printf的地方。这意味着main被传递给main 首先。现在,在这个问题中,int main()给出了以下内容 原型:int main (int argc, char *argv[], char *envp[])。但编译器实际上将函数设置为 请改为使用以下原型:%rdi。实际上存储在argc中的第一个参数 1。这就是程序正在打印Failed to contact JobTracker plugin at None:9290

的原因