函数返回值而不返回语句

Function returns value without return statement

提问人:Pascal Bayer 提问时间:1/10/2011 更新时间:7/24/2018 访问量:31013

问:

为什么以下代码的输出正确?int GGT 没有 return 语句,但代码仍然有效?没有设置全局变量。

#include <stdio.h>
#include <stdlib.h>

int GGT(int, int);

void main() {
    int x1, x2;
    printf("Bitte geben Sie zwei Zahlen ein: \n");
    scanf("%d", &x1);
    scanf("%d", &x2);
    printf("GGT ist: %d\n", GGT(x1, x2));
    system("Pause");
}

int GGT(int x1, int x2) {
    while(x1 != x2) {
        if(x1 > x2) {
            /*return*/ x1 = x1 - x2;
        }
        else {
            /*return*/ x2 = x2 - x1;
        }
    }
}
c 返回值

评论

9赞 Oliver Charlesworth 1/10/2011
将编译器的警告级别调高,您应该会收到一条消息......
0赞 Pascal Bayer 1/10/2011
我收到一条警告消息,但我想知道为什么它确实有效,如果没有,编译器是否设置了返回值?
1赞 Ciro Santilli OurBigBook.com 5/13/2015
定义为 int 但正文中没有 return 语句的 C 函数的可能重复项仍可编译
1赞 ikegami 10/17/2019
请注意(不太具体地)会捕获此错误。-Wreturn-type-Wallgcc

答:

24赞 kriss 1/10/2011 #1

它不应该起作用,当然也不适用于所有编译器和目标操作系统,即使它适用于您的编译器和目标操作系统。

可能的解释是,返回 int 的函数总是返回一些东西,它通常是寄存器的内容。在您的情况下,用于返回值的寄存器可能与用于计算从函数返回之前的最后一个表达式的寄存器相同(在 x86 目标上,当然是 eax)。

话虽如此,允许检测到没有返回的优化编译器完全删除此函数的代码。从此以后,当激活更高的优化级别时,您看到的效果(可能)会消失。

我用 gcc 测试了它:

GCC 无需优化: 输入 10, 20 -> 结果为 10

gcc -O1 输入 10, 20 -> 结果为 1

gcc -O2 输入 10, 20 -> 结果为 0

48赞 ruslik 1/10/2011 #2

至少对于 x86,此函数的返回值应在寄存器中。调用方将把存在的任何内容视为返回值。eax

因为是用作返回寄存器,所以经常被被调用方用作“暂存”寄存器,因为它不需要保存。这意味着它很可能会被用作任何局部变量。由于两者在最后相等,因此更有可能在 中保留正确的值。eaxeax

9赞 Robert Kolner 1/10/2011 #3

在 x86 上,返回值存储在 EAX 寄存器中,该编译器也“意外地”使用它来存储算术运算(或至少减法)的结果。可以通过查看编译器生成的程序集来检查这一点。我同意 kriss - 你不能假设情况总是如此,所以最好显式指定返回值。

评论

0赞 Pascal Bayer 1/10/2011
不要以为这总是有效,但我感兴趣的是,为什么它在这种情况下有效。感谢您的回答!
-1赞 ARA1307 2/8/2013 #4

在这种情况下,GCC 会粘贴“ret”指令,而 clang 会粘贴“ud2”,应用程序会在运行时崩溃。

12赞 dbush 10/17/2019 #5

如果将函数定义为返回值,但未返回值,并且调用函数尝试使用返回值,则调用未定义的行为

这在 C 标准的第 6.9.1p12 节中进行了详细说明:

如果达到 that terminates 函数,并且 函数调用由调用方使用,行为未定义。}

在这种情况下,您很幸运,该程序似乎可以正常工作,但不能保证这一点。如果您使用不同的优化设置或完全不同的编译器进行编译,您最终可能会得到不同的结果。

所以这个故事的寓意是:如果函数说它返回一个值,总是返回一个值。

评论

0赞 Antti Haapala -- Слава Україні 10/17/2019
我关闭了一个问题的副本,该问题的答案解释了 EAX 返回寄存器等机制,并标记了合并问题......
7赞 John Bode 10/17/2019 #6

官方措辞:

6.9.1 函数定义
...
12 如果达到终止函数的 that,并且函数调用的值由 调用方,则行为未定义。
}

其中“未定义的Benavior”是指:

1 未定义的行为
行为,在使用不可移植或错误的程序结构或错误数据时, 本国际标准对此没有要求

2 注意 可能的未定义行为范围从完全忽略情况到不可预测的 结果,在翻译或程序执行过程中以记录的方式行为特征 环境(无论是否发出诊断消息),终止翻译或 执行(发出诊断消息)。

3 示例 未定义行为的一个例子是整数溢出时的行为。

C 2011 在线草案

信不信由你,键入其他内容的函数不需要有语句。它不是由语言语法强制执行的,并且没有非函数必须包含语句的约束。唯一的约束是,如果存在,则函数中的语句不返回任何表达式的值,并且非函数中的语句必须返回表达式的值。voidreturnvoidreturnreturnvoidreturnvoid

为什么会这样?

C 最初没有数据类型,并且无法指定仅针对其副作用而执行而不返回值的子例程。没有返回“nothing”的好方法,因此不需要该语句。C此时也有“隐式”声明 - 你可以定义一个没有类型的函数体,编译器会假设它是类型化的:voidreturnintint

foo( a, b )   // old style parameter declarations
  int a;      // still legal, but no longer really used
  char *b;    // for very good reasons
{
  // do something interesting with a and b
}

foo隐式键入以返回 ,即使没有显式返回任何值。只要调用方不尝试使用不存在的返回值,就可以了。因此,形成了一种约定,其中“过程”(仅为副作用而执行的函数)没有显式类型,也没有语句。intfooreturn

由于遗留代码是永久的,因此在较新版本的 C 标准中,与语句相关的行为没有更改,即使在 C99 中删除了隐式声明也是如此。returnint

评论

0赞 Antti Haapala -- Слава Україні 10/17/2019
我关闭了一个问题的副本,该问题的答案解释了 EAX 返回寄存器等机制,并标记了合并问题。