为什么我的 C++ 程序在我忘记 return 语句时崩溃,而不仅仅是 returning 垃圾?

Why does my C++ program crash when I forget the return statement, rather than just returning garbage?

我最近开始使用 CLang 编译嵌入式 C++ ARM 程序。

在此之前,我使用 GCC 和 C,几乎专门用于嵌入式工作。

我注意到,当我有一个方法 return 是一个值时,我忘记了 return 语句,程序核心转储。除了 "msleep error -1" 之外,我的一个设备驱动程序没有打印任何错误。这是在 FreeBSD 上。

我希望忘记 return 语句只会导致从函数中 returned 垃圾,而不是核心转储。

编辑:我return是一个布尔值,不是指针或对象或任何复杂的东西。即使 return 值无关紧要,程序也会崩溃。

这是怎么回事?

例如:

bool MyClass::DummyFunc() {
  <do some stuff and forget the return value>     
}

其他地方:

if(pMyObj->DummyFunc()) {
  print ("Hey, it's true!\n");
} else {
  print ("Darn, it's false!\n");
}

无论 return 值如何,该代码都不应崩溃。

来自二手资源,因为我不想为 C++ 标准付费,它显然说:

Flowing off the end of a function is equivalent to a return with no value; this results in undefined behavior in a value-returning function.

这是 C++,当你做一些未定义的事情时,你应该预料到它会使你的计算机崩溃并吃掉你的衣服,除非实现另有说明。

I would expect that forgetting the return statement would just result in garbage being returned from the function, not a core dump.

What is going on?

你的期望错了,是怎么回事。

编译器——一个极其复杂的机器——可以自由地假设你的函数return是一个值,因为你保证它是。

那你违背了诺言。

这就是为什么我们不应该对实现细节做出假设,尤其是在编写行为未定义的程序时。您不能直接比较 C++ 代码和 "some data goes into this register and there is this call stack that does precisely this thing".

这有近乎无限的方法可以破坏,因为在将 C++ 代码转换为计算机可读的经过优化的程序的过程中,编译器会进行一系列极其复杂的优化,并且任何一个如果您违反标准规则,它们可能会被折成两半。尝试准确确定程序的任何特定 运行 上发生了什么需要以下知识:

  • CPU 品牌、型号和版本
  • 操作系统品牌、型号和版本
  • 编译器品牌、型号和版本
  • 所有编译器标志
  • 10 年编译器源代码经验
  • 时间机器在程序执行时检查计算机中每一位内存的状态。

实在是不值得

幸运的是,在某些情况下,我们可以在不背离抽象的情况下对这种情况进行额外的合理化:例如,考虑一下当您承诺 return std::string 但没有做到时会发生什么t,然后那个不存在的 std::string 超出了范围。正在随机调用析构函数,这不会顺利进行。

忘记 return 值会导致控制流到达函数末尾。 C 和 C++ 标准都描述了这种情况。请注意,main 是这种情况的一个例外,将单独描述。

Flowing off the end of a function is equivalent to a return with no value; this results in undefined behavior in a value-returning function C++11 Draft pg 136

我对 clang 的经验是,编译器会将 "undefined behavior" 的实例视为 不会发生 并优化路径。这是合法的,因为行为是未定义的。通常 clang 会沿着省略的路径发出非法指令,这样如果 "impossible" 发生,代码就会崩溃,这很可能是你的情况。事实上,编译器然后可以确定调用 DummyFunc() 会导致未定义的行为,因此不会发生,而是开始优化调用主体。

gcc 远 "friendlier" 并尝试生成一些不错的东西,例如 returning 0.

请注意,两个编译器都是正确的,并且正在根据标准生成有效代码。