C语言中带返回值的void函数

5
据我所知,void函数中的return语句会抛出一个错误。
但是在下面的程序中情况并非如此。
这里显示的输出结果为1。这是为什么?
main()
{
    int i=5;
    printf("%d",fun(fun(fun(i))));
}

void fun(int i)
{
    if (i%2)
    {
        return (i+(7*4)-(5/2)+(2*2));
    }
    else
    {
        return (i+(17/5)-(34/15)+(5/2));
    }
}

11
在有原型之前使用 fun 可能会导致混乱的后果。这意味着它返回一个 int,当你稍后定义它时,编译器如何解决这个问题是无法确定的。自 C99 开始将禁止这种操作。 - Pascal Cuoq
7
事实上,这段代码产生的编译器警告行数与其相关代码行数大致相当。这并不是一个好的迹象。 - Andreas Grapentin
2
这是未定义行为。在没有编译器优化的情况下,我得到了1,在有编译器优化的情况下,我得到了0。 - mch
3个回答

7

没有表达式的return语句:

void func(void) {
    return;
}

在一个 `void` 函数中使用 `return` 语句是完全合法的。带表达式的 `return` 语句的合法性取决于你使用的 C 语言版本。
1990 年的 C 标准规定:
“在返回类型为 `void` 的函数中,不得出现带表达式的 `return` 语句。”
1999 年和 2011 年版本的标准都规定:
“在返回类型为 `void` 的函数中,不得出现带表达式的 `return` 语句。只有在返回类型为 `void` 的函数中,才能出现不带表达式的 `return` 语句。”
这是一种约束,意味着违反它的任何程序都必须由编译器发出诊断(可能是非致命警告)。
C90 允许在历史原因上,在一个非 `void` 函数中使用不带表达式的 `return` 语句。ANSI C 之前没有 `void` 关键字,因此无法定义不返回值的函数。程序员会省略返回类型(默认为 `int`),并简单地忽略它。C90 规则允许这样的旧代码编译而不出错。但你仍然可能未从非 `void` 函数返回一个值,如果调用者试图使用(不存在的)结果,则程序的行为是未定义的。1999 年的标准稍微加强了规则。
你程序的另一个问题是在声明可见之前调用 `fun`。在 C99 和以后的规则下,这是非法的(尽管编译器可能只是警告)。按照 C90 规则,这是合法的,但编译器将假定该函数返回 `int`。你程序的行为是未定义的,但你的 `void` 函数 `fun` 可能会表现得好像它返回了一个值,并且对它的调用可能会表现得好像使用了那个值。
C 编译器对某些错误往往比较宽容,以便旧代码(有时是在第一个实际标准发布之前编写的)不被拒绝。但你的编译器至少应该警告你关于 `return` 语句的问题,可能还有关于无效调用的问题。你应该密切关注编译器的警告;它们应该几乎与致命错误一样处理。你应该使用选项增加编译器警告的数量。如果你使用 gcc,请使用 `-std=c90`、`-std=c99` 或 `-std=c11` 以及 `–pedantic` 来执行标准符合性。你可以添加 `-Wall -Wextra` 来启用更多警告。

我不知道这是好是坏,但有时我在返回void的函数中使用相当多的“空”返回。 - awatan
1
@JewelThief:这是完全有效的。尽管从良好的设计角度来看,一些人主张编写仅从一个点返回的函数;早期的 return,有或没有值,可能会使控制流分析更加困难。 - Keith Thompson

4
你提供的代码在C99标准下实际上是无效的,原因有很多,但最痛苦的一个如下所示:
foo.c:5:17: warning: implicit declaration of function 'fun' is invalid in C99 [-Wimplicit-function-declaration]
    printf("%d",fun(fun(fun(i))));
                ^
foo.c:8:6: error: conflicting types for 'fun'
void fun(int i)
     ^
foo.c:5:17: note: previous implicit declaration is here
    printf("%d",fun(fun(fun(i))));
                ^

请注意,如果你提供了 fun() 的函数原型,(你真的应该这样做),那么你将会得到不同的一组错误:
foo.c:7:25: error: passing 'void' to parameter of incompatible type 'int'
    printf("%d",fun(fun(fun(i))));
                        ^~~~~~

-1
因为C语言被编译成二进制代码,从技术上讲,函数总是会返回一个值。在大多数现代处理器中,返回的值会保存在一个寄存器¹中,比如在Intel处理器上是RAX(或者32位系统中的EAX),而你无法从CPU中移除一个寄存器。
当你创建一个空函数时,编译器会忽略返回结果,但它仍然存在。
在你的情况下,fun(fun(fun(i)))²表达式会将i传递给第一个调用,然后在Linux上的Intel处理器上,它会对第二个和第三个调用执行MOV RDI, RAX操作。这只是将RAX中的结果复制到fun()函数的第一个参数中,汇编中对应的是RDI
所以这就是为什么你的代码会有一些“作用”。然而,今天这被认为是“未定义行为”。在过去,只要能让软件工作,就被认为是成功的。任何编译通过的代码都被视为有效的代码...

¹ 在某些处理器上,尤其是一些旧的8位CPU上,可能会使用特定的内存位置,或者调用者必须传递一个地址,以便将返回值写入其中。

² 假设您的C编译器接受这样的代码,但实际上它不应该接受。至少gcc 11.3拒绝编译该代码。


2
并不是每个系统都是带有x86/x64处理器的个人电脑。我想说,在所有可用C语言编程的系统中,这样的系统所占份额远远低于50%。你可能需要修改你的答案,使其更加通用。请注意,并不是每个系统都具备能够在寄存器中返回int类型的处理器。 - the busybee
因为C语言被编译成二进制代码,从技术上讲,函数总是会返回一个值。 -- 不,事实并非如此。问题中的代码不仅具有未定义行为,还违反了多个约束条件,因此严格来说它甚至不是C语言。如果编译器接受该代码,C标准并没有定义它的行为,所以从这个意义上说它具有未定义行为。调用约定,比如将函数结果返回到某个特定寄存器中,完全超出了C语言的范畴。C代码规定的是行为,而不是CPU指令序列。 - Keith Thompson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接