为什么它 return 是一个随机值而不是我给函数的值?
Why does it return a random value other than the value I give to the function?
在 C 程序中,有一个 swap 函数,这个函数接受一个名为 x.I 的参数,期望它通过在 main 函数内的 swap 函数中改变 x 值来 return 它。
当我将参数作为变量赋值时,我想要它,但是当我直接为参数设置整数值时,程序会产生随机输出。
#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;
}
Return 随机值,例如 Value: 779964766
或 Value:1727975774
.
其实在两段代码中,我都给函数赋了一个整数类型的值,即使是相同的值,为什么输出结果不一样?
您需要使用return
或使用指针。
- 使用return函数。
#include <stdio.h>
int swap () {
return 20;
}
int main(void){
int a = swap(100);
printf ("Value: %d", a);
return 0;
}
- 使用指针函数。
#include <stdio.h>
int swap (int* x) {
(*x) = 20;
}
int main(void){
int a;
swap(&a);
printf ("Value: %d", a);
return 0;
}
首先,C 函数是按值调用的:函数中的 int x
arg 是一个 copy。修改它不会修改调用者传递的任何内容的副本,因此您的 swap
没有任何意义。
其次,您正在使用函数的 return 值,但您没有 return
语句。在 C 中(与 C++ 不同),执行从非 void
函数的末尾脱落不是未定义的行为(出于历史原因,在 void
存在之前,函数 returns 类型默认为 int)。但它 是 仍然是调用者未定义的行为 使用 一个 return 值,而函数没有 return
一个.
在这种情况下,returning 100 是未定义行为的影响(使用函数的 return 值,其中执行在没有 return
语句的情况下结束). 这与GCC在调试模式下的编译方式巧合(-O0
):
GCC -O0
喜欢计算 return-值寄存器 中的非常量表达式,例如EAX/RAX 在 x86-64 上。 (这实际上适用于跨架构的 GCC,而不仅仅是 x86-64)。这实际上在 codegolf.SE 个答案上被滥用了;显然有些人宁愿使用 gcc -O0
作为一种语言而不是 ANSI C。请参阅 this "C golfing tips" answer and the comments on it, and 关于为什么 i=j
在函数中将值放入 RAX。请注意,它仅在 GCC 必须将值加载到寄存器中时才有效,而不仅仅是像 add dword ptr [rbp-4], 1
for x++
之类的内存目标增量。
在你的情况下(你的代码由 GCC10.2 on the Godbolt compiler explorer 编译)
int y=100;
将 100 直接存储到堆栈内存(GCC 编译代码的方式)。
int a = swap(y);
将 y
加载到 EAX(无明显原因), 然后 复制到 EDI 以作为参数传递给 swap
.由于 swap
的 GCC asm 没有触及 EAX,在调用之后,EAX=y,所以有效的函数 returns y
.
但是如果你用 swap(100)
调用它,GCC 在设置 args 时不会最终将 100 放入 EAX。
GCC 编译您的 swap
的方式,asm 不会触及 EAX,因此无论 main
剩下什么都被视为 return 值。
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
...
(后面的 ...
重新加载 a
作为 printf
的参数,匹配 ISO C 语义,因为 GCC -O0
将每个 C 语句编译为一个单独的块asm;因此后面的那些不受早期 UB 的影响(与启用优化的一般情况不同),所以只打印 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
请记住 none 这与有效的可移植 C 有关。这(使用留在内存或寄存器中的垃圾)是您在实践中从 C 中看到的一种调用未定义行为的事情,但肯定不是唯一的事情。另请参阅 LLVM 博客中的 What Every C Programmer Should Know About Undefined Behavior。
这个答案只是回答了 asm 中到底发生了什么的字面问题。 (我假设 GCC 未优化,因为这很容易解释结果,而 x86-64 是因为这是一个常见的 ISA,尤其是当人们忘记提及任何 ISA 时。)
其他编译器不一样,GCC开启优化会不一样
在 C 程序中,有一个 swap 函数,这个函数接受一个名为 x.I 的参数,期望它通过在 main 函数内的 swap 函数中改变 x 值来 return 它。
当我将参数作为变量赋值时,我想要它,但是当我直接为参数设置整数值时,程序会产生随机输出。
#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;
}
Return 随机值,例如 Value: 779964766
或 Value:1727975774
.
其实在两段代码中,我都给函数赋了一个整数类型的值,即使是相同的值,为什么输出结果不一样?
您需要使用return
或使用指针。
- 使用return函数。
#include <stdio.h>
int swap () {
return 20;
}
int main(void){
int a = swap(100);
printf ("Value: %d", a);
return 0;
}
- 使用指针函数。
#include <stdio.h>
int swap (int* x) {
(*x) = 20;
}
int main(void){
int a;
swap(&a);
printf ("Value: %d", a);
return 0;
}
首先,C 函数是按值调用的:函数中的 int x
arg 是一个 copy。修改它不会修改调用者传递的任何内容的副本,因此您的 swap
没有任何意义。
其次,您正在使用函数的 return 值,但您没有 return
语句。在 C 中(与 C++ 不同),执行从非 void
函数的末尾脱落不是未定义的行为(出于历史原因,在 void
存在之前,函数 returns 类型默认为 int)。但它 是 仍然是调用者未定义的行为 使用 一个 return 值,而函数没有 return
一个.
在这种情况下,returning 100 是未定义行为的影响(使用函数的 return 值,其中执行在没有 return
语句的情况下结束). 这与GCC在调试模式下的编译方式巧合(-O0
):
GCC -O0
喜欢计算 return-值寄存器 中的非常量表达式,例如EAX/RAX 在 x86-64 上。 (这实际上适用于跨架构的 GCC,而不仅仅是 x86-64)。这实际上在 codegolf.SE 个答案上被滥用了;显然有些人宁愿使用 gcc -O0
作为一种语言而不是 ANSI C。请参阅 this "C golfing tips" answer and the comments on it, and i=j
在函数中将值放入 RAX。请注意,它仅在 GCC 必须将值加载到寄存器中时才有效,而不仅仅是像 add dword ptr [rbp-4], 1
for x++
之类的内存目标增量。
在你的情况下(你的代码由 GCC10.2 on the Godbolt compiler explorer 编译)
int y=100;
将 100 直接存储到堆栈内存(GCC 编译代码的方式)。
int a = swap(y);
将 y
加载到 EAX(无明显原因), 然后 复制到 EDI 以作为参数传递给 swap
.由于 swap
的 GCC asm 没有触及 EAX,在调用之后,EAX=y,所以有效的函数 returns y
.
但是如果你用 swap(100)
调用它,GCC 在设置 args 时不会最终将 100 放入 EAX。
GCC 编译您的 swap
的方式,asm 不会触及 EAX,因此无论 main
剩下什么都被视为 return 值。
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
...
(后面的 ...
重新加载 a
作为 printf
的参数,匹配 ISO C 语义,因为 GCC -O0
将每个 C 语句编译为一个单独的块asm;因此后面的那些不受早期 UB 的影响(与启用优化的一般情况不同),所以只打印 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
请记住 none 这与有效的可移植 C 有关。这(使用留在内存或寄存器中的垃圾)是您在实践中从 C 中看到的一种调用未定义行为的事情,但肯定不是唯一的事情。另请参阅 LLVM 博客中的 What Every C Programmer Should Know About Undefined Behavior。
这个答案只是回答了 asm 中到底发生了什么的字面问题。 (我假设 GCC 未优化,因为这很容易解释结果,而 x86-64 是因为这是一个常见的 ISA,尤其是当人们忘记提及任何 ISA 时。)
其他编译器不一样,GCC开启优化会不一样