我有一个程序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,
我有点不舒服
.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”需要一些ASM和x86-64 ABI
知识。首先,goo.cfoo.c是两个独立的编译
单位。关于foo.c函数,foo唯一知道的是
伪造的原型。
伪造的原型如下:void foo(double);。这是一个函数
这只需要一个双参数。x86-64 ABI要求
双倍通过xmm寄存器(确切的短语
如果类是SSE,则使用下一个可用的向量寄存器,
寄存器按从%xmm0到%xmm7的顺序获取。
这意味着当编译器设置参数来调用
函数,它将通过foo()传递参数。在
简化的asm发生的情况是:

mov 3.0, %xmm0
call foo

现在,%xmm0,在它的一边,相信它会得到一个int
x86-64abi说:“如果类是整数,那么下一个可用的寄存器
使用了序列%rdi、%rsi、%rdx、%rcx、%r8和%r9。第一次
参数应该通过foo()传递。这意味着%rdi
会做如下事情:
mov %rdi, %rsi
mov 0xabcd, %rdi // 0xabcd being the address of the "%d" string
call printf

所以你最终会打印出foo()中的内容,而不是%rsi
但是为什么?您将通过发出以下命令获得一个想法:
%xmm0
1
./a.out a
看到模式了吗?让我们回到简化的程序集:
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

如您所见,在达到./a.out a b之前,没有设置./a.out a b c
传递给printf的地方。这意味着%rdi被传递到foo()
首先。现在,在这个问题中,1给出如下
原型:main。但是编译器实际上将函数设置为
改为使用以下原型:main。因此存储在int main()中的第一个参数实际上是
int main (int argc, char *argv[],char *envp[])。这就是程序打印%rdi的原因。

关于c - 了解由于原型(prototype)不匹配而导致的意外结果(C89),我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/30020963/

10-12 18:16
查看更多