未使用参数中的返回值 [英] Return value in unused parameter

查看:63
本文介绍了未使用参数中的返回值的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在这个高尔夫答案中,我看到了一个窍门,其中返回值是第二个未传递的参数

  int f(i,j){j = i;}int main(){返回f(3);} 

gcc的汇编输出中,看起来就像代码复制 j = i 将结果存储在恰好是返回值的 eax 中.

  f:pushq%rbpmovq%rsp,%rbpmovl%edi,-4(%rbp)movl%esi,-8(%rbp)movl -4(%rbp),%eaxmove%eax,-8(%rbp)ppopq%rbp退回主要的:pushq%rbpmovq%rsp,%rbpmovl $ 3,%edimovl $ 0,%eax打电话给fpopq%rbp退回 

那么,这是否只是因为幸运而发生了?这是gcc记录的吗?它仅适用于 -O0 ,但适用于我尝试过的一堆 i 值, -m32 和一堆不同的版本GCC.

解决方案

gcc -O0 喜欢对返回值寄存器 if a中的表达式求值(GCC -O0 通常只喜欢在retval寄存器中具有值,但这超出了将其作为第一个临时值的范围.)

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

x ++ 这样的东西可以(在x86上)编译为内存目标inc或加起来,不会将值保留在寄存器中,但是赋值通常会保留.因此,请注意,就像GCC对待函数体一样,如 GNU C语句表达式.


没有任何记载,保证或标准化的内容.这是一个实现细节,不是想要让您利用这种优势的东西.

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

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

我在有关Codegolf的答案下的评论中对此进行了论证.SE C高尔夫球技巧Q& A (错误地声称它可以在GCC之外使用).这个答案有4个反对意见(值得更多IMO),但有16个反对意见.因此,社区中的一些成员不同意这是可怕的和愚蠢的.


有趣的事实:在ISO C ++(而不是C)中,执行因非 void 函数的结束而下降是不确定的行为,即使调用者没有执行'<<>使用结果.即使在GNU C ++中也是如此.在 -O0 之外,GCC和clang有时会发出类似 ud2 (非法指令)的代码,用于在没有 return <的情况下到达函数末尾的执行路径/code>.因此,GCC通常不会在此处定义行为(对于ISO C和C ++尚未定义的事情,允许执行这些实现.例如, gcc -fwrapv 将带符号的溢出定义为2的补回.)

但是在ISO C中,非空函数的结尾是合法的:只有在调用者使用返回值的情况下,它才变成UB .如果没有 -Wall ,GCC可能甚至不会发出警告.在不使用return语句的情况下检查函数的返回值

在禁用优化的情况下,不会进行函数内联,因此UB在编译时并不可见.(除非您使用 __ attribute __((always_inline))).


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

有趣的事实:递归 f(i){f(i);} 函数主体在将其复制到第一个arg-pass寄存器之前,会先通过EAX反弹 i .因此,GCC真的很喜欢EAX.

  movl -4(%rbp),%eaxmove%eax,%edimovl $ 0,%eax#没有完整的原型,在AL中传递FP args#打电话给f 

i ++; 不会加载到EAX中;它仅使用内存目标 add 而不加载到寄存器中.值得尝试使用gcc -O0 for ARM.

In this golfing answer I saw a trick where the return value is the second parameter which is not passed in.

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

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

From gcc's assembly output it looks like when the code copies j = i it stores the result in eax which happens to be the return value.

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 

So, did this happen just by being lucky? Is this documented by gcc? It only works with -O0, but it works with a bunch of values of i I tried, -m32, and a bunch of different versions of GCC.

解决方案

gcc -O0 likes to evaluate expressions in the return-value register, if a register is needed at all. (GCC -O0 generally just likes to have values in the retval register, but this goes beyond picking that as the first temporary.)

I've tested a bit, and it really looks like GCC -O0 does this on purpose across multiple ISAs, sometimes even using an extra mov instruction or equivalent. IIRC I made an expression more complicated so the result of evaluation ended up in another register, but it still copied it back to the retval register.

Things like x++ that can (on x86) compile to a memory-destination inc or add won't leave the value in a register, but assignments typically will. So it's note quite like GCC is treating function bodies like GNU C statement-expressions.


This is not documented, guaranteed, or standardized by anything. It's an implementation detail, not something intended for you to take advantage of like this.

"Returning" a value this way means you're programming in "GCC -O0", not C. The wording of the code-golf rules says that programs have to work on at least one implementation. But my reading of that is that they should work for the right reasons, not because of some side-effect implementation detail. They break on clang not because clang doesn't support some language feature, just because they're not even written in C.

Breaking with optimization enabled is also not cool; some level of UB is generally acceptable in code golf, like integer wraparound or pointer-casting type punning being things that one might reasonably wish were well-defined. But this is pure abuse of an implementation detail of one compiler, not a language feature.

I argued this point in comments under the relevant answer on Codegolf.SE C golfing tips Q&A (Which incorrectly claims it works beyond GCC). That answer has 4 downvotes (and deserves more IMO), but 16 upvotes. So some members of the community disagree that this is terrible and silly.


Fun fact: in ISO C++ (but not C), having execution fall off the end of a non-void function is Undefined Behaviour, even if the caller doesn't use the result. This is true even in GNU C++; outside of -O0 GCC and clang will sometimes emit code like ud2 (illegal instruction) for a path of execution that reaches the end of a function without a return. So GCC doesn't in general define the behaviour here (which implementations are allowed to do for things that ISO C and C++ leaves undefined. e.g. gcc -fwrapv defines signed overflow as 2's complement wraparound.)

But in ISO C, it's legal to fall off the end of a non-void function: it only becomes UB if the caller uses the return value. Without -Wall GCC may not even warn. Checking return value of a function without return statement

With optimization disabled, function inlining won't happen so the UB isn't really compile-time visible. (Unless you use __attribute__((always_inline))).


Passing a 2nd arg merely gives you something to assign to. It's not important that it's a function arg. But i=i; optimizes away even with -O0 so you do need a separate variable. Also just i; optimizes away.

Fun fact: a recursive f(i){ f(i); } function body does bounce i through EAX before copying it to the first arg-passing register. So GCC just really loves EAX.

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

i++; doesn't load into EAX; it just uses a memory-destination add without loading into a register. Worth trying with gcc -O0 for ARM.

这篇关于未使用参数中的返回值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆