【问题标题】:Function returns value without return statement没有返回语句的函数返回值
【发布时间】:2011-06-06 09:39:54
【问题描述】:

为什么下面的代码有正确的输出? int GGT 没有返回语句,但代码确实有效吗?没有设置全局变量。

#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 return-value


【解决方案1】:

它不应该工作,当然也不能在所有编译器和目标操作系统上工作,即使它适用于你的。

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

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

我用 gcc 测试过:

没有优化的gcc: 输入 10, 20 -> 结果是 10

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

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

【讨论】:

    【解决方案2】:

    至少对于 x86,这个函数的返回值应该在eax 寄存器中。任何存在的东西都将被调用者视为返回值。

    因为eax用作返回寄存器,所以被调用者经常将其用作“临时”寄存器,因为它不需要保留。这意味着它很有可能被用作任何局部变量。因为最后两者相等,所以eax中留下正确值的可能性更大。

    【讨论】:

      【解决方案3】:

      在 x86 上,返回值存储在 EAX 寄存器中,该编译器“偶然”也使用该寄存器来存储算术运算(或至少是减法)的结果。您可以通过查看编译器生成的程序集来检查这一点。我同意 kriss 的观点——你不能假设总是这样,所以最好明确指定返回值。

      【讨论】:

      • 不要认为这会一直有效,但我很感兴趣,为什么它在这种情况下有效。感谢您的回答!
      【解决方案4】:

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

      【讨论】:

        【解决方案5】:

        如果一个函数被定义为返回一个值但没有,并且调用函数尝试使用返回值,则调用undefined behavior

        这在C standard 的第 6.9.1p12 节中有详细说明:

        如果到达终止函数的},并且 函数调用被调用者使用,行为未定义。

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

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

        【讨论】:

        • 我关闭了一个问题的副本,该问题的答案解释了 EAX 返回寄存器等机制,并标记了要合并的问题...
        【解决方案6】:

        官方用语:

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

        其中“未定义的行为”是指:

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

        2 注 结果,在翻译或程序执行期间以文件的方式表现 环境(无论是否发出诊断消息),终止翻译或 执行(发出诊断消息)。

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

        C 2011 Online Draft

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

        为什么会这样?

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

        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 被隐式键入以返回 int,即使没有显式返回值。只要调用者不尝试使用 foo 不存在的返回值,这是可以的。因此,开发了一种约定,其中“过程”(仅针对副作用执行的函数)没有显式键入,并且没有 return 语句。

        由于遗留代码是永远存在的,在 C 标准的较新版本中,与 return 语句相关的行为并未改变,即使在 C99 中删除了隐式 int 声明。

        【讨论】:

        • 我关闭了一个问题的副本,该问题的答案解释了 EAX 返回寄存器等机制,并标记了要合并的问题..
        猜你喜欢
        • 2011-10-26
        • 2011-10-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-10-03
        相关资源
        最近更新 更多