【问题标题】:Is there a safe version of C++ without undefined behaviour?是否有没有未定义行为的 C++ 安全版本?
【发布时间】:2021-03-26 11:43:52
【问题描述】:

C++ 中未定义的行为很难调试。是否存在不包含任何未定义行为而是抛出异常的 C++ 和标准库版本?我知道这会成为性能杀手,但我只打算在调试模式下编程、调试和编译时使用这个版本,并不真正关心性能。理想情况下,此版本是可移植的,您可以轻松打开/关闭未定义的行为检查。

例如,您可以像这样实现一个安全指针类(仅检查空指针,而不是实际上是否指向有效的内存块):

template <typename T>
class MySafePointer {
     T* value;
public:
      auto operator-> () {
          #ifndef DEBUG_MODE
          assert(value && "Trying to dereference a null pointer");          
          #endif
          return value;
      }
      /* Other Stuff*/

};

如果你想恢复你的表现,用户只需要#undef DEBUG_MODE

是否有执行此操作的 C++ 库/安全版本?

编辑:更改了上面的代码,使其实际上更有意义并且不会引发异常,但断言值是非空的。这个问题只是一个描述性错误消息与崩溃的问题......

【问题讨论】:

  • 有编译器插件,比如-fsanitize=undefined。但是一些未定义的行为只是不可能检测到。
  • 常量表达式中的程序,UB 是不允许的。 (还有一些合法的运行时构造)。
  • @SomeProgrammer: Halting problem 例如。 (没有可观察行为的无限循环是 UB)。
  • @Jarod42:但显然实现可以选择将无限循环实现为无限循环。
  • 根据您在安全方面需要走多远,最好的选择是更改语言。想到 C# 和 rust

标签: c++ undefined-behavior


【解决方案1】:

是否存在不包含任何未定义行为而是抛出异常的 c++ 和标准库版本?

不,没有。正如评论中提到的,有 Address Sanitizer 和 Undefined Behavior Sanitizer 以及许多其他可用于寻找错误的工具,但没有“没有未定义行为的 C++”实现。

如果您想要一种本质上安全的语言,请选择一种。不是 C++。

【讨论】:

  • 太可惜了。
  • @SomeProgrammer,我相信,即使不是大多数,也是许多语言的耻辱。
  • @SomeProgrammer,本质上不是安全的语言。你认识一个吗?顺便说一句,任何没有在其标准中定义某些东西的语言都承认未定义的行为。所以你真的在问是否有一种语言的标准定义了所有可能的场景。你知道一个吗?
  • @SomeProgrammer - C++ 标准明确定义了“未定义”的含义,并且(可以)指定行为未定义的位置。 Java 规范没有这样做,但仍然有(大部分被忽略的)未定义行为的示例。 Java 设计者试图消除 C++ 中未定义的行为(通过定义特定结果或防止它们发生),而没有记录这样做的权衡(例如,某些机器上的运行时成本而不是其他机器上的运行时成本,对某些程序员来说很直观,但对其他)。 Java 被称为“一次编写,到处调试”是有原因的。
  • @supercat,不允许将规则扔出窗外是为什么可以更好地优化某些东西的原因,而不必用大量的 if/else 来弄乱它们?我当然没有你那么专业,所以请随意将我的评论扔出窗外:D
【解决方案2】:

未定义的行为

未定义的行为意味着您的程序最终处于标准未定义其行为的状态。

所以你真正要问的是,是否有一种语言的标准定义了所有可能的场景。

我想不出这样一种语言,原因很简单,程序是由机器运行的,但程序和标准是由人类编写的。

在一些微不足道的情况下防止它的影响

未定义行为的一个小例子是当您通过operator[] 访问std::vector 的越界元素时。与 C 风格的数组完全一样,v[i] 基本上会返回*(v_ + i),其中v_ 是包裹在v 中的指针。这是快速且不安全的。

如果您想安全地访问ith 元素怎么办?您必须更改 std::vector&lt;&gt;::operator[] 的实现。

那么支持DEBUG_MODE 标志会有什么影响呢?本质上,您必须编写两个由#ifdef/(#else/)#endif 分隔的实现。显然这两个实现有很多共同点,所以你可以在代码中多次#-branch。但是...是的,我的底线是,您的要求可以通过更改标准来满足,以强制实施者为所有内容支持两种不同的实施(安全和快速/不安全和慢速)。

顺便说一句,对于这种特定情况,标准确实定义了另一个函数at,它是处理越界情况所必需的。但这就是重点:这是另一个功能。

怀疑

我最近发现 this questionconstexpr 相关的 UB。这是来自the answer Nicol Bolas 的一点引用,

假设,我们可以从 C++ 甚至 C 中删除所有未定义的行为。我们可以先验地明确定义所有内容,并从语言中删除任何无法根据第一原则确定评估的内容。

这让我对我在这里给出的答案感到紧张。

【讨论】:

  • 我的意思是有未定义的行为/崩溃/核心转储,是否有某些版本的 c++ 可以提​​供描述性错误消息?
  • 核心转储可能是最具描述性的错误消息。它显示了崩溃时程序状态的每一个细节!当您尝试修复错误时,还有什么比这更有帮助?
  • @SomeProgrammer 是什么?你身处这片土地上,一切皆有可能,没有什么可以保证?
  • @Enlico 一条描述性错误消息,表明我正在尝试在行...访问空指针,而不仅仅是“分段错误”
  • @SomeProgrammer 堆栈跟踪应该为您提供该信息。
【解决方案3】:

是否有没有未定义行为的安全版本的 c++?

没有。

例如,您可以像这样实现一个安全指针类

如何抛出异常比崩溃更安全?您仍在尝试查找错误以便静态修复它,对吧?

您编写的内容允许您的错误程序继续运行(除非它只是调用terminate,在这种情况下您做了一些工作却完全没有结果),但这并不能使它正确,并且它隐藏了错误而不是帮助你修复它。

是否有执行此操作的 C++ 库/安全版本?

未定义的行为只是一种错误类型,并不总是错误。故意使用非便携式平台功能也可能是标准未定义

不管怎样,假设你捕获了每个未初始化的值、空指针和有符号整数溢出——你的程序仍然会产生错误的结果。

如果你编写的代码不会产生错误的结果,它也不会有 UB。

【讨论】:

  • 对不起……我只是抛出异常以获得描述性错误消息,我从未想过要抓住它。
  • 当然,但关键是取消引用空指针并立即崩溃提供了关于错误的更多信息,而不是引发异常或其他任何事情。将这种错误检查转移到类型系统(如 gsl)中是一种更好的方法。
  • 好吧,在我的机器上我只是得到“核心转储”
  • 您是否尝试查看转储的核心文件?如果您实际上只是得到“中止”或类似情况,请确保启用核心转储并且大小限制是合理的。
  • 核心文件在哪里?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-06-13
  • 2013-04-25
  • 1970-01-01
  • 2012-01-21
  • 2011-10-18
  • 1970-01-01
相关资源
最近更新 更多