未使用参数中的返回值 [英] Return value in unused parameter
问题描述
在这个高尔夫答案中,我看到了一个窍门,其中返回值是第二个未传递的参数
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)中,执行因非 但是在ISO C中,非空函数的结尾是合法的:只有在调用者使用返回值的情况下,它才变成UB .如果没有 在禁用优化的情况下,不会进行函数内联,因此UB在编译时并不可见.(除非您使用 传递第二个参数只会给您分配的东西.它不是一个函数arg 并不重要.但是即使使用 有趣的事实:递归 In this golfing answer I saw a trick where the return value is the second parameter which is not passed in. From gcc's assembly output it looks like when the code copies So, did this happen just by being lucky? Is this documented by gcc? It only works with gcc I've tested a bit, and it really looks like GCC Things like 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- 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 With optimization disabled, function inlining won't happen so the UB isn't really compile-time visible. (Unless you use Passing a 2nd arg merely gives you something to assign to. It's not important that it's a function arg. But Fun fact: a recursive 这篇关于未使用参数中的返回值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋! void
函数的结束而下降是不确定的行为,即使调用者没有执行'<<>使用结果.即使在GNU C ++中也是如此.在 -O0
之外,GCC和clang有时会发出类似 ud2
(非法指令)的代码,用于在没有 return <的情况下到达函数末尾的执行路径/code>.因此,GCC通常不会在此处定义行为(对于ISO C和C ++尚未定义的事情,允许执行这些实现.例如,
gcc -fwrapv
将带符号的溢出定义为2的补回.)> -Wall
,GCC可能甚至不会发出警告.在不使用return语句的情况下检查函数的返回值 __ attribute __((always_inline))
). -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.int f(i, j)
{
j = i;
}
int main()
{
return f(3);
}
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
-O0
, but it works with a bunch of values of i
I tried, -m32
, and a bunch of different versions of 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.)-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.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.
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.)-Wall
GCC may not even warn. Checking return value of a function without return statement__attribute__((always_inline))
).
i=i;
optimizes away even with -O0
so you do need a separate variable. Also just i;
optimizes away.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.