C 未使用参数中的返回值

C 未使用参数中的返回值,c,gcc,x86,return-value,kernighan-and-ritchie,C,Gcc,X86,Return Value,Kernighan And Ritchie,在这篇文章中,我看到了一个技巧,返回值是第二个未传入的参数 int f(i, j) { j = i; } int main() { return f(3); } 从它看来,当代码复制j=i时,它将结果存储在eax中,这恰好是返回值 f: pushq %rbp movq %rsp, %rbp movl %edi, -4(%rbp) movl %esi, -8(%rbp)

在这篇文章中,我看到了一个技巧,返回值是第二个未传入的参数

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

int main() 
{
    return f(3);
}
从它看来,当代码复制
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。

GCC
-O0
喜欢计算返回值寄存器中的表达式,如果需要寄存器的话。
(GCC
-O0
通常只希望retval寄存器中有值,但这超出了将其作为第一个临时值的范围。)

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

可以(在x86上)编译到内存目标inc或add的
x++
之类的东西不会将值保留在寄存器中,但赋值通常会保留。因此,值得注意的是,GCC将函数体视为


这不是任何文档、保证或标准化的内容。这是一个实现细节,不是为了让您利用这样的优势

以这种方式“返回”值意味着您正在“GCC-O0”中编程,而不是C。代码高尔夫规则的措辞规定程序必须至少在一个实现上工作。但我的理解是,它们应该工作的原因是正确的,而不是因为一些副作用的实现细节。它们在clang上破裂不是因为clang不支持某些语言功能,只是因为它们不是e文是用C写的

打破启用优化也不酷;在代码高尔夫中,某种程度的UB通常是可以接受的,比如整数环绕或指针投射类型双关,这可能是人们有理由希望得到良好定义的东西。但这纯粹是滥用一个编译器的实现细节,而不是语言特性

我在下面的评论中论证了这一点(该评论错误地宣称它超越了GCC)。这个答案有4张反对票(在国际海事组织中应该得到更多),但有16张赞成票。因此,一些社区成员不同意这是可怕和愚蠢的


有趣的事实:在ISO C++(而不是C)中,执行失败的非代码>虚空函数是未定义的行为,即使调用方不使用结果。即使在GNU C++中也是如此;在> O0 GCC和CLAN有时会发出代码,如<代码> UD2< /COD>(非法指令)。对于一个函数的执行路径,它没有一个函数的结尾,没有<代码>返回<代码>。所以GCC一般不定义这里的行为(允许实现ISO C和C++的未定义的东西。例如,<代码> Gcc-FWRAPV < /Cult>将签名溢出定义为2的补全包。) 但是在ISO C中,从非void函数的末尾掉下来是合法的:只有调用方使用返回值时,它才会变成UB。没有

-Wall
GCC甚至可能不会发出警告

禁用优化后,函数内联将不会发生,因此UB在编译时不会真正可见。(除非使用
\uuuu attribute\uuuu((始终内联))


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

有趣的事实:递归的
f(i){f(i);}
函数体在复制到第一个arg传递寄存器之前,确实会通过EAX反弹
i
。所以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中;它只使用内存目的地<代码>添加<代码>,不加载到登记器中。值得尝试使用GCC—O0为ARM。< /P>链接的页面中的代码有<代码> i= j;< />代码。这个代码中有“代码> j= i;< /Cuff>,这是交换的。- c+C++的例子,非空虚函数结束。GCC警告和代码生成中的行为差异。