了解由于原型不匹配而导致的意外结果 (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.c
和foo.c
是两个独立的编译
单位。 foo.c
对 foo
函数唯一了解的是
伪造的原型。
仿冒原型如下: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
.
的原因
我有一个程序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.c
和foo.c
是两个独立的编译
单位。 foo.c
对 foo
函数唯一了解的是
伪造的原型。
仿冒原型如下: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
.