未使用参数中的返回值

时间:2019-08-09 22:35:22

标签: c gcc x86 return-value kernighan-and-ritchie

在此golfing answer中,我看到了一个窍门,其中返回值是第二个未传入的参数。

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

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

gcc's assembly output看来,当代码复制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一堆值-m32,以及一堆不同版本的GCC。

1 个答案:

答案 0 :(得分:4)

认为 gcc -O0恰好像GNU C statement-expressions这样对待函数体。那是一个实现细节,不是想要让您利用这种优势的东西。

gcc -O0确保最后计算的赋值表达式的值保留在返回值寄存器中。 (GCC -O0通常只喜欢在retval寄存器中具有值,但这不仅仅将其作为第一个临时值。)

我已经测试了一下,确实看起来GCC -O0是跨多个ISA故意这样做的,有时甚至使用额外的mov指令或等效指令。 IIRC我使表达式更加复杂,因此求值结果在另一个寄存器中结束,但仍将其复制回retval寄存器。


没有任何记载,保证或标准化的内容。

以这种方式“返回”值表示您正在“ GCC -O0”而不是C中进行编程。代码高尔夫球规则的措辞表示,程序必须至少在一个实施。但是我的理解是,它们应该出于正确的理由起作用,而不是因为一些副作用的实现细节。它们在使用clang时中断并不是因为clang不支持某些语言功能,而是因为它们甚至都不是用C编写的。

启用优化功能也很酷;某种程度的UB通常在代码高尔夫中是可以接受的,例如整数环绕或指针投射类型的修剪是人们可能希望明确定义的事情。但这纯粹是滥用了一个编译器的实现细节,而不是语言功能。

我在the relevant answer on Codegolf.SE C golfing tips Q&A下的评论中指出了这一点(错误地声称它在GCC之外有效)。这个答案有4个反对意见(值得更多IMO),但有16个反对意见。因此,社区中的一些成员不同意这是可怕的和愚蠢的。

在ISO C中,执行不落在非void函数的末尾是未定义的行为,即使调用方使用结果 >。即使在GNU C中也是如此。在-O0之外的地方,GCC和clang有时会发出类似ud2(非法指令)的代码,用于在没有return的情况下到达函数末尾的执行路径。因此,GCC通常不会定义行为(对于ISO C未定义的事情,允许执行哪些实现。例如gcc -fwrapv将带符号的溢出定义为2的补码环绕。)


传递第二个参数只会给您一些东西。它不是一个函数arg 并不重要。但是i=i;即使使用-O0也会进行优化,因此您确实需要一个单独的变量。也只有i;可以优化。

有趣的事实:递归f(i){ f(i); }函数体确实通过EAX反弹i,然后将其复制到第一个arg-pass寄存器。因此,GCC真的很喜欢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 for ARM。