c - 为什么它返回一个随机值而不是我给函数的值?
问题描述
在 C 程序中,有一个交换函数,该函数接受一个名为 x 的参数,我希望它通过更改主函数内交换函数中的 x 值来返回它。
当我将参数赋值为变量时,我想要它,但是当我直接为参数设置整数值时,程序会产生随机输出。
#include <stdio.h>
int swap (int x) {
x = 20;
}
int main(void){
int y = 100;
int a = swap(y);
printf ("Value: %d", a);
return 0;
}
此代码的输出:100(如我所愿)
但是这段代码:
#include <stdio.h>
int swap (int x) {
x = 20;
}
int main(void){
int a = swap(100);
printf ("Value: %d", a);
return 0;
}
返回随机值,例如Value: 779964766
or Value:1727975774
。
实际上,在两个代码中,我给函数一个整数类型的值,即使是相同的值,但为什么输出不同?
解决方案
首先,C 函数是按值调用的:int x
函数中的 arg 是一个copy。修改它不会修改调用者传递的任何内容的副本,因此您的swap
意义为零。
其次,您正在使用函数的返回值,但您没有return
声明。在 C 中(与 C++ 不同),执行从非函数的末尾脱落并不是未定义的行为void
(由于历史原因,以前void
存在,并且函数返回默认为 int 的类型)。但是,当函数没有返回值时,调用者使用返回值仍然是未定义的行为。return
在这种情况下,返回 100 是未定义行为的结果(使用函数的返回值,其中执行在没有return
语句的情况下结束)。 这是 GCC 在调试模式下编译的巧合(-O0
):
GCC-O0
喜欢在返回值寄存器中计算非常量表达式,例如 x86-64 上的 EAX/RAX。(这实际上适用于跨架构的 GCC,而不仅仅是 x86-64)。这实际上在 codegolf.SE 答案中被滥用;显然,有些人宁愿将高尔夫gcc -O0
作为一种语言而不是 ANSI C。请参阅此“C 高尔夫技巧”答案及其评论,以及有关为什么在函数中将值放入 RAX 的SO Q&A 。i=j
请注意,它仅在 GCC 必须将值加载到寄存器中时才有效,而不仅仅是像add dword ptr [rbp-4], 1
forx++
或其他那样进行内存目标增量。
在您的情况下(您的代码由 Godbolt编译器资源管理器上的 GCC10.2 编译)
int y=100;
将 100 直接存储到堆栈内存(GCC 编译代码的方式)。
int a = swap(y);
加载y
到 EAX(没有明显原因),然后复制到 EDI 以作为 arg 传递给swap
. 由于 GCC 的 asm forswap
不涉及 EAX,因此在调用后,EAX=y,因此函数有效地返回y
.
但是如果你用 调用它swap(100)
,GCC 在设置参数时不会最终将 100 放入 EAX。
GCC 编译你swap
的 .asm 的方式不会触及 EAX,所以main
剩下的任何东西都被视为返回值。
main:
...
mov DWORD PTR [rbp-4], 100 # y=100
mov eax, DWORD PTR [rbp-4] # load y into EAX
mov edi, eax # copy it to EDI (first arg-passing reg)
call swap # swap(y)
mov DWORD PTR [rbp-8], eax # a = EAX as the retval = y
...
但与你的其他主要:
main:
... # nothing that touches EAX
mov edi, 100
call swap
mov DWORD PTR [rbp-4], eax # a = whatever garbage was there on entry to main
...
(后者作为 arg for...
重新加载,匹配 ISO C 语义,因为 GCC将每个 C 语句编译为单独的 asm 块;因此,后面的不受早期 UB 的影响(与启用优化的一般情况不同),所以只打印内存位置中的任何内容。)a
printf
-O0
a
该swap
函数编译如下(再次,GCC10.2 -O0):
swap:
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], edi
mov DWORD PTR [rbp-4], 20
nop
pop rbp
ret
请记住,这些都与有效的可移植 C 无关。这(使用留在内存或寄存器中的垃圾)是您在实践中从 C 中看到的一种调用未定义行为的东西,但肯定不是唯一的东西。另请参阅LLVM 博客中的每个 C 程序员应该了解的关于未定义行为的知识。
这个答案只是回答 asm 中到底发生了什么的字面问题。(我假设 GCC 未优化,因为这很容易解释结果,而 x86-64 因为这是一个常见的 ISA,尤其是当人们忘记提及任何 ISA 时。)
其他的编译器不一样,开启优化的GCC也会不一样。
推荐阅读
- python - TypeError: child_process_1.spawn is not a function
- google-apps-script - Google Apps 脚本 getSheetByName 返回 null
- javascript - 三角形相似吗?
- python - for 循环,numpy 和 sqlite
- javascript - 如何在javascript中检查数组是否已满
- java - Spring Boot 更改覆盖异常响应
- python - matplotlib中的pcolormesh重新缩放轴
- r - 给定另一列的三个最小值,我需要找到一列中的值
- jenkins - 如何使用 CURL 将多级单选参数传递给詹金斯作业?
- python - 如何确定两个熊猫系列是否在给定的时间间隔内