12

我知道这条消息是什么意思,我只是想知道为什么它不是错误消息,而只是警告?

在这种情况下会发生什么?例如,假设我有一个函数

int f()
{
}

当我调用它时会发生什么?
在这种情况下,编译器是否添加了“未初始化”的返回int
或者缺少返回可能导致堆栈损坏?
还是(绝对)未定义的行为

使用 gcc 4.1.2 和 4.4.3 测试


编辑:阅读我理解一件事的答案,阅读评论 - 另一个..

好的,让我们总结一下:这是未定义的行为。那么,这意味着有可能导致堆栈损坏,对吗?(这甚至意味着,我的电脑可能会开始通过麦克风插孔向我扔烂番茄,尖叫——“你做了什么???”)。

但如果是这样,那么为什么这里的最高答案说,堆栈损坏不会发生,同时,行为是未定义的?

和未定义的?尝试使用“未返回值”的调用者,或者只是函数的结尾未定义,如果它必须返回值,但它没有?

或者这不是未定义的行为,而只是尝试使用该值(未返回,d'oh!)的用户将“接收”未定义的?换句话说 - 只是一些垃圾价值,不会发生更多的事情吗?

4

7 回答 7

11

A: 不,缺少返回不会导致堆栈损坏

答:是的,如果调用者试图读取和/或使用(未定义!)返回值,则行为将是“未定义的”。

PS:

这是对 C++ 的引用:

C++03 §6.6.3/2:

从函数的末尾流出相当于没有值的返回;这会导致值返回函数中的未定义行为。

于 2012-09-11T19:20:40.883 回答
7

您询问了 C 和 C++。两种语言的规则不同。

在 C 中,仅当调用者尝试使用函数返回的值时,行为才被定义。如果你有:

int func(void) {
    /* no return statement */
}

...

func();

那么行为是明确定义的。

在 C++ 中,无论调用者是否尝试使用结果,行为都是未定义的(如果函数被调用)。(这是出于历史原因;ANSI C 之前没有void关键字,并且不打算返回值的函数通常(隐式)定义为 return int。)

John Bode 的回答已经引用了2011 ISO C 标准 6.9.1p12的N1570 草案:

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

paulsm4 引用了 C++ 标准;引用最新的 2011 版本,6.6.3p2:

从函数的末尾流出相当于return没有值的 a;这会导致值返回函数中的未定义行为。

导致 C 允许返回值的函数无法返回值的历史原因,只要调用者不使用该值,不适用于 C++,其设计不受避免需要的强烈影响打破旧的(ANSI C 之前的)代码。

在 C(从 C99 开始)和 C++ 中,main函数都是一种特殊情况;}在不执行 return的情况下到达结束main相当于 a return 0;。(C 允许main返回除 之外的实现定义的类型int;在这种(罕见的)情况下,落到最后会向宿主环境返回未指定的终止状态。)

当然,return从返回值的函数中省略语句,或者有一个可能的执行路径没有到达return语句,是一个坏主意。int它仅在用作void和 in的替代的古老遗留 C 代码中才有意义main(尽管即使是 for main,我个人也喜欢有一个明确的return 0;)。

于 2013-05-15T18:00:57.963 回答
4

该标准认为它未定义。

实际上,将读取为返回值保留的内存或寄存器。有什么就有什么。

于 2012-09-11T19:23:53.823 回答
4

C 2011 草案 N1570

6.9.1 函数定义

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

“未定义”仅表示语言标准不要求编译器以任何特定方式处理这种情况;任何行动都被认为是“正确的”。编译器可以自由地发出诊断和停止翻译,或者发出诊断和完整的翻译(这就是你所看到的),或者完全忽略问题。

至于实际的运行时行为,这取决于以下内容:

  • 调用者如何使用返回值?
  • 使用的调用约定是什么?
  • 底层架构如何表现?

等等

于 2012-09-11T20:05:13.387 回答
2

编译器不需要对此进行诊断,因为在某些情况下它很难。所以规则是行为是未定义的。

于 2012-09-11T19:22:37.713 回答
0

我在 Linux 64 位 GCC 4.63 上进行了一个简单的测试让我们在实践中看看 GCC 是如何组装这样的东西的。

我创建了一个简单的例子

这是具有正常返回值的 test.c

int main()
{
    return 0;
}

这是 test.c 的 GCC 汇编器输出:

main:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movl    $0, %eax
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc

这是没有返回值的 test2.c

int main()
{
}

这是 test2.c 的 GCC 汇编器输出:

main:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc

基本上我们可以看到缺少以下行。

    movl    $0, %eax

这一行将一个值移动到eax寄存器,这是函数的返回值。如果该函数在现实生活中使用,eax可能包含垃圾值的寄存器将表示main()...的返回值

于 2013-05-15T19:40:15.630 回答
0

我在 g++ 中进行了测试。似乎你得到了一个包含随机内容的对象,即它不调用任何构造函数。当我只处理整数时,这意味着我得到一个随机数。当我处理字符串时,我遇到了分段违规。我不知道这是否是你所说的腐败,但这很糟糕。

至于你的第一个问题,我会更进一步。我想知道为什么这是一个默认禁用的警告!这对我来说似乎很重要。

#include <string>
#include <iostream>

class X
{
private:
  int _x;
public:
  X(int x) : _x(x) { } // No default construcor!                            
  int get() const { return _x; }
};

X test1(int x)
{
  if (x > 0)
    return X(x);
  // warning: control reaches end of non-void function                      
}

class Y
{
private:
  std::string _y;
public:
  Y(std::string y) : _y(y) { } // No default construcor!                    
  std::string get() const { return _y; }
};

Y test2(std::string y)
{
  if (y.length() > 3)
    return Y(y);
  // warning: control reaches end of non-void function                      
}

int main(int, char**)
{
  std::cout<<"4 -> "<<test1(4).get()<<std::endl;
  std::cout<<"-4 -> "<<test1(-4).get()<<std::endl;
  std::cout<<"stop -> "<<test2("stop").get()<<std::endl;
  std::cout<<"go -> "<<test2("go").get()<<std::endl;
}
于 2013-12-30T18:57:04.320 回答