当前位置: 首页 > 知识库问答 >
问题:

当从非void函数的末尾掉落时,写入未使用的参数所返回的值

越健
2023-03-14

在这个高尔夫球答案中,我看到了一个技巧,其中返回值是第二个没有传入的参数。

int f(i, j) 
{
    j = i;   
}

int main() 
{
    return f(3);
}

从gcc的程序集输出来看,当代码复制j=i时,它将结果存储在eax中,这恰好是返回值。

f:
        pushq   %rbp
        movq    %rsp, %rbp
        movl    %edi, -4(%rbp)
        movl    %esi, -8(%rbp)
        movl    -4(%rbp), %eax
        movl    %eax, -8(%rbp)
        nop
        popq    %rbp
        ret
main:
        pushq   %rbp
        movq    %rsp, %rbp
        movl    $3, %edi
        movl    $0, %eax
        call    f
        popq    %rbp
        ret 

那么,这仅仅是因为运气好吗?这是否由gcc记录?它只适用于-O0,但它适用于我尝试的一系列值i


共有1个答案

娄阳舒
2023-03-14
匿名用户

如果需要寄存器,gcc-O0喜欢在返回值寄存器中计算表达式。(GCC-O0通常只喜欢在retval寄存器中有值,但这不仅仅是将其作为第一个临时值。)

我测试了一下,看起来GCC -O0在多个isa上故意这样做,有时甚至使用额外的< code>mov指令或等效指令。IIRC I做了一个更复杂的表达式,所以求值的结果在另一个寄存器中结束,但是它仍然把它复制回retval寄存器。

x这样可以(在x86上)编译到内存目标inc或add的东西不会将值留在寄存器中,但赋值通常会。所以值得注意的是,GCC正在处理像GNU C语句表达式这样的函数体。

这没有任何文件、保证或标准化。这是一个实现细节,不是让你像这样利用的东西。

以这种方式“返回”值意味着您正在“GCC -O0”而不是C中编程。代码高尔夫规则的措辞说,程序必须至少在一个实现上工作。但我对此的理解是,他们应该出于正确的理由工作,而不是因为一些副作用的实现细节。它们在叮当声中中断不是因为叮当声不支持某些语言功能,只是因为它们甚至没有用C语言编写。

在启用html" target="_blank">优化的情况下中断也不酷;某种程度的UB在代码高尔夫中通常是可以接受的,比如整数环绕或指针转换类型双关语是人们可能有理由希望定义良好的东西。但这纯粹是滥用编译器的实现细节,而不是语言特性。

我在Codegolf.SE的相关回答下的评论中争论了这一点。

有趣的事实:在ISO C(而不是C)中,让执行脱离非< code>void函数的末尾是未定义的行为,即使调用者不使用结果。即使在GNU C中也是如此;在< code>-O0之外,GCC和clang有时会发出类似于< code>ud2(非法指令)的代码,用于到达函数末尾而没有< code>return的执行路径。所以GCC一般不定义这里的行为(哪些实现被允许做ISO C和C没有定义的事情)。例如< code>gcc -fwrapv将带符号溢出定义为2的补码回绕。)

但在 ISO C 中,从非 void 函数的末尾掉下来是合法的:只有当调用方使用返回值时,它才会变成 UB。没有-墙海湾合作委员会甚至可能不会发出警告。检查函数的返回值,不带返回语句

禁用优化后,函数内联将不会发生,因此UB在编译时并不可见。(除非使用< code > _ _ attribute _ _((always _ inline)))。

传递第二个参数只是给你一些东西去赋值。它是一个函数参数并不重要。但是< code > i = i即使使用< code>-O0也会进行优化,因此您确实需要一个单独的变量。也只是<代码>我;进行优化。

有趣的事实:一个递归的< code > f(I){ f(I);}函数体在将< code>i复制到第一个arg-passing寄存器之前,确实通过EAX反弹它。所以海湾合作委员会真的很喜欢EAX。

        movl    -4(%rbp), %eax
        movl    %eax, %edi
        movl    $0, %eax             # without a full prototype, pass # of FP args in AL
        call    f

<代码>i未加载到EAX中;它只使用内存目的地add,而不加载到寄存器中。值得尝试的gcc-O0用于ARM。

 类似资料:
  • 尾部调用优化对返回void的函数的递归调用有效吗?例如,我有一个函数,void fun() 在这里,编译器不会知道,调用fun()是最后一条语句。那么尾部调用优化是否只针对返回某些值的函数?

  • C和C中有没有办法让返回void的函数按照未指定的顺序求值? 我知道函数参数是按未指定的顺序计算的,因此对于不返回void的函数,这可用于以未指定的顺序计算这些函数: 合格编译器编译的合法C和C代码可以按任何顺序打印< code>hi 、< code>bye和< code>moo。这并不是未定义的行为(鼻魔是无效的),有效的输出不止一个,但不一定是无限的,一个兼容的编译器甚至不需要确定它产生了什么

  • 我正在学习指针是如何工作的,但我不明白这段代码中的一件事。在void*函数中返回int就像一个咒语,但是返回float就不是了。

  • 问题内容: 我有一个返回类对象或nil的函数。该功能的目的是检查是否存在。聊天ID存储在MySQL中。如果ID存在,则执行Firebase引用以获取快照,然后获取对象。如果ID不存在,则返回nil: 但是,在我得到 void函数中非预期的非无效返回值。 关于我可能会想念的任何想法? 问题答案: 问题是您试图从一个封闭内部返回一个非空值,该值仅从封闭内部返回,但是由于该封闭期望一个空返回值,因此您会

  • 我有一个经过修改的二分搜索函数,它在一个向量中找到最小值(最接近L的值--一个已知值)和最大值(最接近R的值--相同的已知值)。不幸的是,当我试图编译代码时,我得到了这个警告,我不知道为什么:控制到达非空函数的末端。

  • 出于我无法控制的原因,我必须在我的C代码中实现这个函数: 调用此函数时,编译器是否忽略它,或者是否仍然进行调用?例如: 两行代码的执行时间是相同的,还是第一行需要更长的时间?