问题描述
在C程序中,有一个交换函数,该函数接受一个名为x的参数.我希望它通过更改主函数内部交换函数中的x值来返回它.
In a C program, there is a swap function and this function takes a parameter called x.I expect it to return it by changing the x value in the swap function inside the main function.
当我将参数值视为变量时,我会想要它,但是当我直接为参数设置整数值时,程序会产生随机输出.
When I value the parameter as a variable, I want it, but when I set an integer value directly for the parameter, the program produces random outputs.
#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(根据需要)
Output of this code: 100 (As I wanted)
但是此代码:
#include <stdio.h>
int swap (int x) {
x = 20;
}
int main(void){
int a = swap(100);
printf ("Value: %d", a);
return 0;
}
随机返回值,例如 Value:779964766
或 Value:1727975774
.
实际上,在两个代码中,我给函数提供了一个整数类型的值,即使是相同的值,但为什么输出却不同?
Actually, in two codes, I give an integer type value into the function, even the same values, but why are the outputs different?
推荐答案
首先,C函数是按值调用的:函数中的 int x
arg是副本.修改它不会修改调用者传递的任何内容的副本,因此您的 swap
有意义.
First of all, C functions are call-by-value: the int x
arg in the function is a copy. Modifying it doesn't modify the caller's copy of whatever they passed, so your swap
makes zero sense.
第二,您正在使用函数的返回值,但没有 return
语句.在C语言中(与C ++不同),执行到非 void
函数的末尾(由于历史原因,在 void
存在之前,函数返回)并不是不确定的行为类型默认为int).但是当函数没有返回
一个时,调用方使用一个返回值仍然是 未定义的行为.
Second, you're using the return value of the function, but you don't have a return
statement. In C (unlike C++), it's not undefined behaviour for execution to fall off the end of a non-void
function (for historical reasons, before void
existed, and function returns types defaulted to int). But it is still undefined behaviour for the caller to use a return value when the function didn't return
one.
在这种情况下,返回100是未定义行为的结果(使用没有执行 return
语句的情况下执行结束的函数的返回值).这是GCC在调试模式( -O0
)编译方式的巧合:
In this case, returning 100 was the effect of the undefined behaviour (of using the return value of a function where execution falls off the end without a return
statement). This is a coincidence of how GCC compiles in debug mode (-O0
):
GCC -O0
喜欢评估返回值寄存器中的非常量表达式,例如x86-64上的EAX/RAX.(实际上,跨架构的GCC确实如此,而不仅仅是x86-64).实际上,这在codegolf.SE答案中被滥用了.显然,有些人宁愿以 gcc -O0
作为一种语言而不是ANSI C打高尔夫球.请参见和对此的评论,以及此SO问答集有关为什么 i = j
在将值放入RAX的函数中.请注意,它仅在GCC必须将值加载到寄存器中时才起作用,而不仅仅是像 add dword ptr [rbp-4],1
那样为 x ++
的内存目标增量.等等.
GCC -O0
likes to evaluate non-constant expressions in the return-value register, e.g. EAX/RAX on x86-64. (This is actually true for GCC across architectures, not just x86-64). This actually gets abused on codegolf.SE answers; apparently some people would rather golf in gcc -O0
as a language than ANSI C. See this "C golfing tips" answer and the comments on it, and this SO Q&A about why i=j
inside a function putting a value in RAX. Note that it only works when GCC has to load a value into registers, not just do a memory-destination increment like add dword ptr [rbp-4], 1
for x++
or whatever.
在您的情况下(由GCC10.2编译您的代码在Godbolt编译器浏览器上)
In your case (with your code compiled by GCC10.2 on the Godbolt compiler explorer)
int y = 100;
将100直接存储到堆栈内存(GCC编译代码的方式).
int y=100;
stores 100 directly to stack memory (the way GCC compiles your code).
int a = swap(y);
将 y
加载到EAX中(没有明显原因),然后复制到EDI以作为用于 swap
的arg.由于用于 swap
的GCC汇编程序不会触摸EAX,因此在调用之后EAX = y,因此该函数有效地返回了 y
.
int a = swap(y);
loads y
into EAX (for no apparent reason), then copies to EDI to pass as an arg to swap
. Since GCC's asm for swap
doesn't touch EAX, after the call, EAX=y, so effectively the function returns y
.
但是,如果您使用 swap(100)
进行调用,则GCC最终不会在设置参数时将100放入EAX.
But if you call it with swap(100)
, GCC doesn't end up putting 100 into EAX while setting up the args.
GCC编译您的 swap
的方式,而asm不会触及EAX,因此,剩下的 main
都将被视为返回值.
The way GCC compiles your swap
, the asm doesn't touch EAX, so whatever main
left there is treated as the return value.
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
...
但与您的其他主要人员一样:
But with your other main:
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
...
(稍后的 ...
将 a
重新加载为 printf
的arg,与ISO C语义匹配,因为GCC -O0
将每个C语句编译到一个单独的asm块中;因此,较晚的UB不会影响较晚的UB(与在启用优化的一般情况下不同),因此只需打印 a
的存储位置.)
(The later ...
reloads a
as an arg for printf
, matching the ISO C semantics because GCC -O0
compiles each C statement to a separate block of asm; thus the later ones aren't affected by the earlier UB (unlike in the general case with optimization enabled), so do just print whatever's in a
's memory location.)
swap
函数的编译方式如下(同样,GCC10.2 -O0):
The swap
function compiles like this (again, 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中看到的那种调用未定义行为的事情之一,但肯定不是唯一的事情.另请参见每个C程序员应了解的未定义内容行为(来自LLVM博客).
Keep in mind none of this has anything to do with valid portable C. This (using garbage left in memory or registers) one of the kinds of things you see in practice from C that invokes undefined behaviour, but certainly not the only thing. See also What Every C Programmer Should Know About Undefined Behavior from the LLVM blog.
这个答案只是回答了asm中到底发生了什么的字面问题.(我假设未优化的GCC是因为它很容易解释结果,而x86-64是因为这是常见的ISA,尤其是当人们忘记提及任何ISA时.)
This answer is just answering the literal question of what exactly happened in asm. (I'm assuming un-optimized GCC because that easily explains the result, and x86-64 because that's a common ISA, especially when people forget to mention any ISA.)
其他编译器不同,如果启用优化,GCC也将不同.
Other compilers are different, and GCC will be different if you enable optimization.
这篇关于为什么它返回我给函数赋予的值以外的随机值?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!