为什么它 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: 779964766Value:1727975774.

其实在两段代码中,我都给函数赋了一个整数类型的值,即使是相同的值,为什么输出结果不一样?

您需要使用return或使用指针。

  1. 使用return函数。
#include <stdio.h>

int swap () {

    return 20;
    
}

int main(void){
    
    int a = swap(100);   

    printf ("Value: %d", a);

    return 0;
}
  1. 使用指针函数。
#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开启优化会不一样