[英]Return value in unused parameter
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.从gcc 的汇编输出看来,当代码复制
j = i
它将结果存储在eax
,而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
So, did this happen just by being lucky?那么,这只是运气好吗? Is this documented by gcc?
这是由 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.它只适用于
-O0
,但它适用于我尝试过的一堆i
值, -m32
和一堆不同版本的GCC。
gcc -O0
likes to evaluate expressions in the return-value register, if a register is needed at all. gcc
-O0
喜欢在返回值寄存器中计算表达式,如果需要寄存器的话。 (GCC -O0
generally just likes to have values in the retval register, but this goes beyond picking that as the first temporary.) (GCC
-O0
通常只是喜欢在 retval 寄存器中有值,但这不仅仅是选择它作为第一个临时值。)
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.我已经测试了一点,看起来 GCC
-O0
确实是故意跨多个 ISA 执行此操作,有时甚至使用额外的mov
指令或等效指令。 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. IIRC I 使表达式更复杂,因此计算结果在另一个寄存器中结束,但它仍然将其复制回 retval 寄存器。
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.像
x++
这样可以(在 x86 上)编译为内存目标 inc 或 add 的东西不会将值留在寄存器中,但赋值通常会。 So it's note quite like GCC is treating function bodies like GNU C statement-expressions .所以值得注意的是,GCC 正在处理像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.以这种方式“返回”一个值意味着您正在使用“GCC -O0”而不是 C 进行编程。代码高尔夫规则的措辞表明程序必须至少处理一个实现。 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.
它们在 clang 上中断并不是因为 clang 不支持某些语言功能,只是因为它们甚至不是用 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.
某种程度的 UB 在代码高尔夫中通常是可以接受的,例如整数环绕或指针转换类型双关语是人们可能合理希望得到明确定义的东西。 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).我在 Codegolf.SE C 高尔夫技巧问答的相关答案下的评论中争论了这一点(错误地声称它在 GCC 之外有效)。 That answer has 4 downvotes (and deserves more IMO), but 16 upvotes.
该答案有 4 票反对(值得更多 IMO),但有 16 票赞成。 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 .有趣的事实:在 ISO C++(但不是 C)中,即使调用者不使用结果,执行在非
void
函数的末尾也是未定义的行为。 This is true even in GNU C++;即使在 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
.在
-O0
GCC 之外,clang 有时会发出像ud2
(非法指令)这样的代码,用于到达函数末尾而没有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. eg gcc -fwrapv
defines signed overflow as 2's complement wraparound.)所以 GCC 通常不会在这里定义行为(允许哪些实现对 ISO C 和 C++ 未定义的事情做。例如
gcc -fwrapv
将有符号溢出定义为 2 的补码环绕。)
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 .但是在 ISO C 中,从非 void 函数的末尾脱落是合法的:只有在调用者使用 return value 时它才成为 UB 。 Without
-Wall
GCC may not even warn.没有
-Wall
GCC 甚至可能不会发出警告。 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.禁用优化后,函数内联不会发生,因此 UB 在编译时并不真正可见。 (Unless you use
__attribute__((always_inline))
). (除非您使用
__attribute__((always_inline))
)。
Passing a 2nd arg merely gives you something to assign to.传递第二个参数只会给你分配一些东西。 It's not important that it's a function arg .
它是一个函数 arg 并不重要。 But
i=i;
但是
i=i;
optimizes away even with -O0
so you do need a separate variable.即使使用
-O0
也会优化掉,因此您确实需要一个单独的变量。 Also just i;
也只是
i;
optimizes away.优化掉。
Fun fact: a recursive f(i){ f(i); }
有趣的事实:递归
f(i){ f(i); }
f(i){ f(i); }
function body does bounce i
through EAX before copying it to the first arg-passing register. f(i){ f(i); }
功能身体确实反弹i
将其复制到第一个参数传递寄存器之前通过EAX。 So GCC just really loves EAX.所以 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++;
doesn't load into EAX;不会加载到 EAX 中; it just uses a memory-destination
add
without loading into a register.它只是使用内存目标
add
而不加载到寄存器中。 Worth trying with gcc -O0 for ARM.值得尝试使用 gcc -O0 for ARM。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.