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

Understanding an unexpected result due to an unmatched 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    , %rsp
    movabsq 13937818241073152, %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    , %rsp
    movl    %edi, -4(%rbp)
    movl    -4(%rbp), %eax
    movl    %eax, %esi
    movl    $.LC0, %edi
    movl    [=15=], %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”需要一点 ASM 和 x86-64 ABI 知识。首先,goo.cfoo.c是两个独立的编译 单位。 foo.cfoo 函数唯一了解的是 伪造的原型。

仿冒原型如下:void foo(double);。这是一个功能 只需要一个双参数。 x86-64 ABI 要求 双打通过 xmm 寄存器传递(确切的措辞 is '如果 class 是 SSE,则使用下一个可用的向量寄存器, 寄存器的顺序是从 %xmm0 到 %xmm7.'.

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

mov 3.0, %xmm0
call foo

现在,foo() 认为它会收到一个整数。这 x86-64 ABI 说:'如果 class 是整数,下一个可用的寄存器 使用序列 %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.

的原因