当调用带有警告"control reaches end of non-void function"的函数时,(实际上)会发生什么?
What (actually) happens, when a function with the warning "control reaches end of non-void function" is called?
我知道这条消息是什么意思,我只是想知道为什么它不是错误消息,而只是一个警告?
在这种情况下会发生什么?例如,假设我有一个函数
int f()
{
}
当我调用它时会发生什么?
在这种情况下,编译器是否会添加"未初始化"int
的返回?
或者缺少返回可能会导致堆栈损坏?
还是(绝对(未定义的行为?
使用 gcc 4.1.2 和 4.4.3 进行测试
编辑:阅读答案我理解一件事,阅读评论 - 另一件事。
好吧,让我们总结一下:这是未定义的行为。那么,这意味着,可能会导致堆栈损坏,对吧?(这甚至意味着,我的电脑可能会开始通过麦克风插孔向我扔烂番茄,尖叫 - "你做了什么???"(。
但如果是这样,那么为什么这里的首要答案说,堆栈腐败不会发生,同时,行为是不确定的?
和未定义方面?调用者,尝试使用"未返回值",或者只是函数的末尾是未定义的,如果它必须返回值,但它没有?
或者它不是未定义的行为,只有尝试使用该值的用户(未返回,d'oh!(将"接收"未定义的值?换句话说 - 只是一些垃圾价值,不会再发生什么?
答:不,缺少返回不会导致堆栈损坏
答:是的,如果调用方尝试读取和/或使用(未定义!(返回值,则行为将是"未定义的"。
附注:
以下是对C++的引用:
C++03 §6.6.3/2:
从函数末尾流出等效于没有的返回 价值;这会导致值返回中出现未定义的行为 功能。
你问了C和C++。两种语言的规则不同。
在 C 中,仅当调用方尝试使用函数返回的值时,行为才未定义。如果您有:
int func(void) {
/* no return statement */
}
...
func();
那么行为就被很好地定义了。
在C++中,无论调用方是否尝试使用结果,行为都是未定义的(如果调用函数的话(。 (这是出于历史原因;ANSI C之前没有void
关键字,并且通常(隐式(定义不用于返回值的函数返回int
。
John Bode的回答已经引用了2011年ISO C标准的N1570草案6.9.1p12:
如果达到终止函数的 },并且 函数调用由调用方使用,行为未定义。
paulsm4引用了C++标准;引用了最新的2011年版本6.6.3p2:
只要调用方不使用值,导致 C 允许值返回从函数末尾流出相当于
return
没有 价值;这会导致值返回中出现未定义的行为 功能。
函数无法返回值的历史原因不适用于C++,其设计没有受到避免破坏旧(ANSI C 之前(代码的需要的强烈影响。
在 C(从 C99 开始(和 C++ 中,main
函数都是特例;达到 main
的收盘}
而不执行返回等效于return 0;
。(C 允许main
返回 int
以外的实现定义类型;在这种(罕见(情况下,从末尾掉下来会向主机环境返回未指定的终止状态。
当然,从值返回函数中省略 return
语句,或者具有未到达return
语句的可能执行路径是一个坏主意。它只在使用int
作为void
替身的古代遗留C代码中有意义,并且在main
中有意义(尽管即使对于main
,我个人也喜欢有一个明确的return 0;
(。
标准认为它是未定义的。
实际上,将为返回值保留的内存或寄存器。 凡有的,就有的。
C 2011 草案 N1570
">6.9.1 函数定义
...
12 如果达到终止函数的}
,并且函数调用的值由调用方,行为未定义。
未定义"只是意味着语言标准不需要编译器以任何特定方式处理这种情况;任何操作都被认为是"正确的"。 编译器可以自由地发出诊断并停止翻译,或者发出诊断和完整的翻译(这就是你所看到的(,或者完全忽略问题。
至于实际的运行时行为,这取决于以下内容:
- 调用方如何使用返回值?
- 使用的调用约定是什么?
- 底层架构的行为方式如何?
等。
编译器不需要诊断这一点,因为在某些情况下很难。所以规则是行为是未定义的。
我在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()
...
我用 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;
}
- "delete"在 C++ 中实际上做了什么?
- 您好,我实际上想了解以下代码.有人可以详细说明代码它到底在做什么吗?为什么它会在第 31 行崩溃
- *(int*)&data[18]在这段代码中实际上做了什么?
- mOffsetMatrix在Assimp中实际上做了什么?
- 当我们在 C++ 中说"initialize the object"时,它实际上意味着什么?
- C++ 中向量的 .size() 实际上有什么作用?
- 'return *this'实际上做了什么?
- CPACK_DEB_PACKAGE_COMPONENT实际上做了什么
- 声明是否无效()合法,它实际上是什么?
- 在 IUnknown 中添加和发布,它们实际上做了什么?
- 实际上,什么是负数的模
- vk::D eviceQueueCreateFlags() 实际上做了什么?
- 什么时候 constexpr 实际上不是 constexpr
- C++:去引用实际上在做什么
- boost::asio::io_service::run() 实际上做了什么
- C 如果实际上是继承了类,则内部发生了什么
- 无法理解C++中的枚举,它们实际上是什么?
- 当调用带有警告"control reaches end of non-void function"的函数时,(实际上)会发生什么?
- 实际上什么返回类型有一个新的
- glutswapbuffers实际上做了什么