我已经在gcc中成功编译并执行了以下代码:
#include <stdio.h>
int foo()
{
}
int main()
{
int i = 12345;
i = foo();
printf("i is: %d", i);
}
输出结果为:
i is: 0
因此,gcc允许我不从函数foo()
返回并使foo()
返回0
。
此行为是否仅适用于gcc或其他C标准是否也有此行为(根据我的理解,gcc不符合任何C标准)?
答案 0 :(得分:9)
如果函数缺少return语句¹,C标准都不要求编译器产生错误或警告。
在所有C标准中,如果控制流在没有return
的情况下到达非void函数的末尾,则行为是未定义的,然后使用函数的返回值(如在代码中)。
因此,如果“允许”意味着“将其指定为合法的,明确定义的行为”,那么C标准都不允许您的代码。如果你的意思是“不需要实现来产生错误”,那么所有这些都可以。
请注意,如果更改i
的定义(不添加foo
),示例程序中return
的值可以轻松更改。你不能依赖它为0.没有规则说“如果没有回报,则返回0” - 既不是标准也不是GCC的实施。根据标准,它是未定义的,在GCC中它只是当时在返回寄存器中发生的任何事情。
¹在一般情况下,这将是不可判定的。人们可以做什么,例如Java执行并定义规则,以便拒绝一些其他有效的函数定义,但没有一个C标准执行此操作。
答案 1 :(得分:8)
gcc会发出警告:
警告:控制到达非空函数的末尾[-Wreturn-type] |
(免责声明:我从不同版本的gcc获得不同的结果。确保使用-Wall -Wextra -pedantic-errors
进行编译。)
在这种情况下发生的事情不在C标准范围内。标准只是说(C11 6.9.1 / 12):
如果到达了终止函数的},并且调用者使用了函数调用的值,则行为是未定义的。
在您的情况下,调用者通过将其存储在i
中来确实使用返回值。所以这是未定义的行为 - C标准不包含该行为,编译器没有义务通知您。什么事情都可能发生。因此,在大多数情况下,这应该被视为一个错误。
答案 2 :(得分:5)
嗯,标准中没有任何内容阻止您编写非法代码,但是,它确实提到这是未定义的行为。
引用C11
,章节§6.9.1
如果到达了终止函数的
}
,则使用函数调用的值 调用者,行为未定义。
答案 3 :(得分:2)
C89陈述(至少在草案中):
If a return statement without an expression is
executed, and the value of the function call
is used by the caller, the behavior is undefined.
这意味着您可以像编写代码一样合法。这不是错误。 它是未定义的行为,但它在语法上是合法的。因为它并非特定&#34;非法&#34;并且语法上允许一个遵循C89的编译器可能只是编译它并且结果将是未定义的...但就编译器而言它是一个合法的程序。
这就是为什么即使使用&#34; -pedantic&#34;它可能不会被拒绝。未定义的行为意味着它所说的内容:它未定义会发生什么。请注意,编译器可能会完全优化未定义的行为(这是好的,因为它无论如何都是未定义的行为,因此编译器可以做任何想做的事情)。
答案 4 :(得分:0)
我认为在这种情况下,未定义的行为在最流行的编译器之间很常见。如果您反汇编foo
函数,几乎每个版本的gcc都会找到这些说明:
push rbp
mov rbp, rsp
nop
pop rbp
ret
此代码无所事事,正是您想要的。但是,每当期望函数的结果时,它甚至不遵循调用约定规则。结果应该由eax/rax
寄存器传递给调用者。在foo
中,此寄存器甚至未被修改,这就是您获得未定义行为的原因。
省略回归就像是在说:“我不想遵循你给我的规则”。 clang
生成类似的代码,我猜其他编译器(除非应用了更多选项或优化)以相同的方式推理。没有什么可以阻止你这样做,既不是编译器。未定义的行为是这种自由的代价。
编译器对编写的代码充满信心的证明是eax/rax
寄存器实际上被认为是函数结果的存储。在您的情况下,eax
之前从未在该代码中使用,并且其最后一个值为零。但试试这个:
int i = 12345;
int m = 12345;
m *= 3;
i = foo();
printf("i is: %d", i);
输出可能将是37035,即使这是m
的值。