【问题标题】:Why can we not create trivially constructible objects using malloc if the trivial default constructor performs no action?如果平凡的默认构造函数不执行任何操作,为什么我们不能使用 malloc 创建平凡的可构造对象?
【发布时间】:2020-03-22 01:34:34
【问题描述】:

我很难理解以下从cppreference 引用的关于琐碎默认构造函数的段落。我已经搜索了stackoverflow,但仍然没有得到明确的答案。所以请帮忙。

平凡的默认构造函数是不执行任何操作的构造函数。所有与 C 语言兼容的数据类型(POD 类型)都可以简单地默认构造。然而,与 C 不同的是,不能通过简单地重新解释适当对齐的存储来创建具有普通默认构造函数的对象,例如使用 std::malloc 分配的内存:placement-new 需要正式引入新对象并避免潜在的未定义行为。

具体来说,如果微不足道的默认构造函数什么都不做,为什么我们不能重新解释存储并假装存在具有给定类型的对象?您能否提供一些示例来说明这会导致的潜在的未定义行为

【问题讨论】:

  • 编译器最重要的工作不是编译源代码,而是拒绝可能无效的代码。当你使用 malloc() 时它不能这样做。
  • 原因很简单。程序员做疯狂事情的​​机会越少,编译器做疯狂事情的​​机会就越多(积极优化)。
  • 出于类似的原因,您不能只使用*reinterpret_cast<float*>(&someNonFloatObject) = 0.1f;。 C++ 有对象和对象生命周期的概念,在抽象机上指定,仅仅因为没有 CPU 指令从存储创建对象并不意味着在抽象机上没有区别。
  • @HansPassant 拒绝所有代码的编译器会拒绝所有无效代码。无论如何,拒绝有 UB 的程序不是编译器的工作。

标签: c++ language-lawyer


【解决方案1】:

P0593R5“为低级对象操作隐式创建对象”给出了这个例子:

struct X { int a, b; };
X *make_x() {
  X *p = (X*)malloc(sizeof(struct X));
  p->a = 1;
  p->b = 2;
  return p;
}

并解释:

使用 C++ 编译器编译时,此代码具有未定义的行为,因为 p->a 尝试写入 X 对象的 int 子对象,而此程序从未创建 X 对象或 @ 987654328@子对象。

[intro.object]p1 (C++17 Draft N4659)

object 由定义、new-expression 在隐式更改联合的活动成员或创建临时对象时创建。

...这个程序没有做这些事情。

在实践中这是可行的,并且 UB 情况被认为是标准中的缺陷,而不是其他任何东西。本文的整个目标是提出一种在不破坏其他内容的情况下解决该问题和类似情况的方法。

【讨论】:

    【解决方案2】:

    出于“纯粹”的原因。

    替代方案和实际现状是每个存储区域将同时包含适合该存储的所有对象。一些委员会成员不安对现状感到不安,并且很多人担心在同一个地方拥有无限多个对象(处于虚拟的、未初始化的状态)的概念。

    从来没有人能够证明一个存储区域中有无限多个对象的逻辑问题。

    因为标准中的不同部分存在相互矛盾的内容,委员会成员决定认真对待标准中最糟糕的部分之一。

    此外,如果您真的认真对待标准的那一部分,则严格不允许使用字符串文字。

    【讨论】:

    • 严格禁止使用字符串字面量。关于 type_info 对象也有类似的 CWG 问题。你有没有报告过'bout string literals?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-15
    • 2020-12-08
    • 1970-01-01
    • 1970-01-01
    • 2018-04-11
    • 2019-07-27
    相关资源
    最近更新 更多