【问题标题】:Why does it return a random value other than the value I give to the function?为什么它返回一个随机值而不是我给函数的值?
【发布时间】:2021-05-29 07:09:26
【问题描述】:

在 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: 779964766Value:1727975774

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

【问题讨论】:

  • touch 不返回任何内容。此外,x 是一个局部变量,更改它不会对调用者产生影响。还有swap 甚至在哪里?这和组装有什么关系?
  • 启用编译器警告,例如使用gcc -Wall
  • return 语句从 touch() 中丢失,您不能只声明 int x = 20; 并期望函数返回 20
  • @Jester我忘了写交换,我编辑了。如果交换函数返回 x,它总是返回 20,我不希望这样。我想从外面干涉这个
  • @GovindParmar 不,我想用我从参数中得到的值替换 x 的值。所以我不想定义一个局部变量。

标签: c assembly gcc undefined-behavior


【解决方案1】:

首先,C 函数是按值调用的:函数中的int x arg 是一个副本。修改它不会修改调用者传递的任何内容的副本,因此您的 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。请参阅this "C golfing tips" answer 及其上的cmets,以及this SO Q&A 关于为什么i=j 在函数中将值放入RAX 的原因。请注意,它仅在 GCC 必须将值加载到寄存器中时才有效,而不仅仅是像 add dword ptr [rbp-4], 1x++ 或其他任何东西做一个内存目标增量。


在您的情况下(您的代码由 GCC10.2 on the Godbolt compiler explorer 编译)

int y=100; 将 100 直接存储到堆栈内存(GCC 编译代码的方式)。

int a = swap(y);y 加载到 EAX(没有明显原因),然后 复制到 EDI 以作为 arg 传递给 swap。由于 swap 的 GCC 的 asm 不涉及 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
...

(后面的...a 重新加载为printf 的arg,匹配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

请记住这些都与有效的可移植 C 无关。这(使用留在内存或寄存器中的垃圾)是您在 C 中看到的一种调用未定义行为的东西,但肯定不是唯一的东西。另请参阅 LLVM 博客中的 What Every C Programmer Should Know About Undefined Behavior

这个答案只是回答 asm 中到底发生了什么的字面问题。 (我假设 GCC 未优化,因为这很容易解释结果,而 x86-64 因为这是一个常见的 ISA,尤其是当人们忘记提及任何 ISA 时。)

其他编译器不同,开启优化后GCC会不同。

【讨论】:

  • 是的,这就是我真正想学习的。人们因误解我的问题而得分减分,尽管那是我真正想知道的。非常感谢!
  • @devilsgrin:我想知道,因为你标记了 asm。您可以通过实际将编译器生成的 asm 包含在问题中 中,或者至少说明您正在使用哪种架构的编译器,可以使问题变得更好。并询问它为什么会起作用,或者表明您知道它不是有效的 C 的东西,并且您想知道 asm 中的什么巧合使它起作用。问题的编写方式(除了 [assembly] 标签)听起来像是一个关于如何正确使用 C 的问题。我并不感到惊讶,它得到了很多反对票,以及像 Danylo 的答案(我赞成)。
  • 是的,我给程序集贴上了标签,因为我想知道在程序集方面这是怎么回事,但由于我提出问题的方式而被误解了。我会考虑您的建议,再次非常感谢您的良好回复和解释:) @Peter Cordes
  • @devilsgrin:您现在仍然可以编辑问题,让未来的读者更清楚,如果他们想知道同样的事情,也可以让他们更容易通过搜索找到。此外,因此整理问题的未来编辑不会倾向于删除 [assembly] 标签,就像有人已经做过的那样。您进行了编辑以将标签放回原处,但在编辑时没有做任何其他事情来使问题更清晰。
  • 是的,我知道。很快我会以更好的方式编辑问题。
【解决方案2】:

你需要使用return或者使用指针。

  1. 使用返回函数。
#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;
}

【讨论】:

  • 我知道该怎么做,我只是想知道后台发生了什么。还是谢谢!
猜你喜欢
  • 2021-05-24
  • 1970-01-01
  • 2018-07-14
相关资源
最近更新 更多