当一个具有警告“控制到达非空函数结束”的函数被调用时,实际发生了什么?

时间:2012-09-11 19:18:43

标签: c++ c

我知道这条消息的含义,我只是想知道为什么不是错误消息,只是一个警告?

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

int f()
{
}

当我打电话时会发生什么? 在这种情况下,编译器是否会添加“非初始化”int的返回? 或者丢失的返回可能导致堆栈损坏? 或者(绝对)未定义的行为

使用gcc 4.1.2和4.4.3

进行测试

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

好的,让我们总结一下:它是未定义的行为。然后,这意味着,可能导致堆栈损坏,对吧? (甚至意味着,我的电脑可能会开始通过麦克风插孔将腐烂的西红柿扔到我身上,尖叫着 - “你做了什么?”)。

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

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

或者它不是未定义的行为,只是试图使用该值的用户(未返回,噢!)将“接收”未定义的?换句话说 - 只是一些垃圾价值,没有更多的事情可以发生?

7 个答案:

答案 0 :(得分:11)

答:不,缺少的回报不会导致堆栈损坏

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

PS:

以下是C ++的引用:

  

C ++03§6.6.3/ 2:

     

离开函数末尾相当于没有返回   值;这会导致值返回时出现未定义的行为   功能

答案 1 :(得分:7)

你问过C和C ++。这两种语言的规则不同。

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

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

...

func();

然后行为定义明确。

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

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

  

如果达到终止某个功能的} ,则该值为   函数调用由调用者使用,行为未定义。

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

  

离开函数末尾相当于return没有   值;这会导致值返回时出现未定义的行为   功能

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

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

当然,从值返回函数中省略return语句,或者有一条未达到return语句的可能执行路径,这是一个坏主意。仅在使用int作为void的替代品的古代遗留C代码中,以及在main中(尽管即使对于main,我个人也喜欢一个明确的return 0;)。

答案 2 :(得分:4)

标准认为它未定义。

实际上,将读取为返回值保留的存储器或寄存器。无论那里有什么。

答案 3 :(得分:4)

C 2011 draft N1570

6.9.1函数定义

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

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

关于实际的运行时行为,这取决于:

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

等。

答案 4 :(得分:2)

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

答案 5 :(得分: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()的返回值......

答案 6 :(得分: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;
}