【问题标题】:What happens when we do not use return statement in the calling function in C?如果我们在 C 中的调用函数中不使用 return 语句会发生什么?
【发布时间】:2019-04-24 08:21:46
【问题描述】:

我想知道下面程序的输出。主要关心的是当我们在调用函数中不使用 return 语句时会发生什么?

int sum(int x, int y)
{
    //return x+y; 
}

int main()
{
    int res=0;
    res=sum(1,2);
    printf("res = %d\n",res);
}

在上面的函数sum(),如果我用return x+y;它会打印 3 作为输出吗?

如果我不使用 return 语句会怎样?

【问题讨论】:

  • 这是未定义的行为。对我来说,它打印1。任何事情都可能发生,并且没有任何保证。可能发生的情况是它只是在堆栈上取了一些随机的垃圾值。
  • 我们不经常运行此类程序,因为我们在编译器中使用了高警告设置。
  • 揭示了问题,并立即告诉程序员(他们没有有意地忽略编译器发出的警告)代码中的问题区域究竟在哪里,应该在继续进行之前修复。
  • @n.m.或者,也许我们被教导如何通过阅读 K&R 书来创建未定义的行为,第一个代码示例:main() { printf("hello, world\n"); }。这是从 C90 编写的,所以没有隐式返回 0...
  • @n.m.不,现在很特别。这不是写 K&R 的时候。我们必须假设调用者(OS)可能使用程序的返回码。

标签: c return-value


【解决方案1】:

参见the C11 draft standard 中的 6.9.1(函数定义)/12(C99 中出现相同的语言):

  1. 如果到达终止函数的},并且调用者使用了函数调用的值,则行为未定义。

(在 C90 中,6.6.6.4(return 语句)中大部分是等效语言:

如果执行了不带表达式的return 语句,并且调用者使用了函数调用的值,则行为未定义。到达终止函数的} 等效于在没有表达式的情况下执行return 语句。

不同之处在于(从 C99 开始)在非 void 函数中没有表达式的 return; 是错误的。)

在您的情况下,达到sum}(未执行return 语句)并使用返回值:

res=sum(1,2);

因此,您的代码具有未定义的行为:任何事情都可能发生,从 res 中的垃圾值到无限循环或崩溃。

【讨论】:

  • 我不会说垃圾价值。这取决于架构以及函数调用约定在编译器中的实现方式。它是UB,但它根本不是垃圾结果。根据您的程序,如果您了解架构 + 编译器等,您可以轻松预测。
【解决方案2】:

melpomene's answer 从纯语言的角度来看是 100% 正确的:从没有返回值的非 void 函数返回总是会导致未定义的行为。在那之后,所有的赌注都被取消了。

我要补充一下,可能会发生什么:

当你调用一个函数时,编译器有一套规则,它遵循关于如何将参数和返回值传递给被调用者的规则。这些规则类似于“如果第一个参数是整数类型,则在调用被调用者之前将其放入寄存器eax”。因此,如果您说foo(42),编译器会发出代码以将42 加载到寄存器eax 中,然后调用foo,然后它只检查它在寄存器eax 中找到的值以了解是什么通过了。

返回值也是如此。调用者期望返回值的位置(这可能是寄存器或堆栈上的内存位置)有一个已定义的位置,被调用者有义务在其中实际放置一个合理的值。

因此,当您忘记命名返回值时,根本不会设置返回值。无论发生在相应的寄存器/内存位置中的什么,都将被调用者解释为返回值。 真正有效地“传回”给调用者的内容 100% 取决于callee 恰好被编译,它可能是确定性的。考虑这段代码sn-p:

int getSecret() {
    return 42;
}

int checkSecret(int guess) {
    int secret = getSecret();
    //return secret == guess;
}

int main() {
    printf("The secret is %d.\n", checkSecret(0));
}

如果您编译并运行此代码,您可能会发现它正确打印了秘密值42。为什么?很简单:当getSecret() 返回秘密值时,它将它放在checkSecret() 期望找到返回值的位置。这可能恰好与main() 期望找到checkSecret() 的返回值的寄存器相同。

从语言的角度来看,这完全没问题:当checkSecret() 在没有设置返回值的情况下返回时,语言允许任何事情发生,包括泄露秘密。 这就是为什么忘记返回值可能是一个安全漏洞。它可能允许攻击者提取他们不应该知道的信息,或者触发在正确行为中不可能的代码执行路径,因为函数“返回”的值不在预期的返回值范围内。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-13
    • 2017-06-11
    • 2019-12-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多