【问题标题】:Is there valid "use cases" for Undefined Behaviour?未定义行为是否有有效的“用例”?
【发布时间】:2013-04-25 11:16:30
【问题描述】:

我找到了一段带有 UB 的代码,并被告知将其保留在代码中,并附有说明它是 UB 的注释。仅使用 MSVC2012。

代码本身有一个 Foo 对象的原始数组,然后将该数组转换为带有 reinterpret_cast<char*>char*,然后在其上调用 delete casted_array(像这样,而不是 delete[])。

像这样:

Foo* foos = new Foo[500];

char* CastedFoos = reinterpret_cast<char*>(foos);

delete CastedFoos;

根据标准 5.3.5/3,这显然是未定义的行为。

显然,这段代码的作用是避免调用析构函数作为优化。

我想知道,实际上是否存在将 UB 留在代码中的地方可以被视为有效

另外,就我而言,将上述内容留在代码中并不聪明,对吗?

【问题讨论】:

  • 从技术上讲,UB 是一个 UB。一个特定的实现可能会展示一种 UB 类型的特定重复行为,但这并不能保证该行为不可移植。
  • 几乎所有特定于实现的扩展都是标准 C++ 中未定义的行为,使用扩展肯定有正当理由。
  • 别管这个问题,但为什么这是必要的?如果Foo 没有任何有用的可破坏的东西,只需给它一个微不足道的析构函数。如果确实如此,并且您接受了泄漏,那么为什么还要费心释放内存呢?
  • 我想将~Foo() 更改为什么都不做太有意义了,而::operator delete(foos) 太简单了? ;-)
  • 无论如何,我更喜欢放置新对象创建。然后就可以适当的释放内存了,是否是UB“受限于”相关的析构函数有没有作用。 (但当然是循环和never via array-placement-new。)

标签: c++11 undefined-behavior


【解决方案1】:

这完全取决于你的观点。

举一个极端的例子:在 C++03 中,线程是未定义的行为。一旦您拥有多个线程,您的程序的行为就不再由 C++ 标准定义。

然而,大多数人会说线程是有用的

当然,根据 C++ 标准,多线程可能是 UB,但个别编译器并没有它视为未定义。他们提供了额外的保证,即多线程将按您的预期工作

抽象地谈论 C++ 时,UB 没有任何用处。怎么可能?你不知道会发生什么。

但在特定应用程序中,特定编译器编译的特定代码运行在特定操作系统上,您有时可能知道一块 UB 是 (1) 安全的,并且 (2) 最终会产生某种有益效果。

【讨论】:

    【解决方案2】:

    C++ 标准对“未定义行为”的定义如下:

    本标准没有要求的行为

    因此,如果您希望您的代码可移植到不同的编译器和平台,那么您的代码不应依赖于未定义的行为,因为在这些情况下,程序(由编译您的代码的不同编译器生成)的操作可能会有所不同。

    如果您不关心可移植性,那么您应该检查您的编译器是否记录了它在感兴趣的情况下的行为方式。如果它没有记录它所做的事情(也没有必要),请注意编译器可能会更改它所做的事情而不会在不同版本之间发出警告。另请注意,它的行为可能是不确定的。因此,例如,它可能会在 1% 的时间内崩溃,这在临时测试中您可能不会注意到,但稍后会在它投入生产时回来并咬住您。因此,即使您使用的是一个编译器,依赖未定义的行为可能仍然是个坏主意。

    关于您的具体示例,您可以重写它以达到相同的效果(不是调用析构函数,而是回收内存),但不会导致未定义的行为。分配一个std::aligned_storage来保存Foo数组,调用placement newaligned_storage上构造Foo数组,然后当你想释放数组时,释放aligned_storage而不调用placement delete .

    当然这仍然是一个糟糕的设计,可能会导致内存泄漏或其他问题,具体取决于 Foo::~Foo() 应该做什么,但至少它不是 UB。

    【讨论】:

    • 谢谢你,这是有道理的。
    猜你喜欢
    • 2020-06-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多