为什么(如果是这样的话)标准说用 memcpy 复制未初始化的内存是 UB?

Why (if that is the case) does the standard say that copying uninitialized memory with memcpy is UB?

当一个class成员在构造的时候不能有合理的意义, 我没有初始化它。显然这只适用于 POD 类型,你不能 NOT 使用构造函数初始化一个对象。

这样做的好处,除了节省 CPU 周期初始化一些东西 一个没有意义的值,是我可以检测到这些的错误使用 变量与 valgrind;当我只给出这些变量时这是不可能的 一些随机值。

例如,

struct MathProblem {
  bool finished;
  double answer;

  MathProblem() : finished(false) { }
};

数学题解决(完成)前没有答案。提前初始化 answer 是没有意义的(比如 - 零),因为这可能不是答案。 answer只有在finished设置为true后才有意义。

因此在初始化之前使用 answer 是一个错误并且完全可以成为 UB。

但是,answer 在初始化之前的一个普通副本目前也是 UB(如果我理解正确的话),这没有意义:默认的复制和移动构造函数应该只是能够制作一个简单的副本(又名,好像使用 memcpy),初始化与否:我可能想将此对象移动到容器中:

v.push_back(MathProblem());

然后使用容器内的副本。

移动具有未初始化的、可简单复制的成员的对象是否确实被标准定义为 UB?如果是这样,为什么?好像没啥意思。

Is moving an object with an uninitialized, trivially copyable member indeed defined as UB by the standard?

取决于成员的类型。标准说:

[basic.indet]

When storage for an object with automatic or dynamic storage duration is obtained, the object has an indeterminate value, and if no initialization is performed for the object, that object retains an indeterminate value until that value is replaced ([expr.ass]).

If an indeterminate value is produced by an evaluation, the behavior is undefined except in the following cases:

  • If an indeterminate value of unsigned ordinary character type ([basic.fundamental]) or std​::​byte type ([cstddef.syn]) is produced by the evaluation of:

    • the second or third operand of a conditional expression,
    • the right operand of a comma expression,
    • the operand of a cast or conversion ([conv.integral], [expr.type.conv], [expr.static.cast], [expr.cast]) to an unsigned ordinary character type or std​::​byte type ([cstddef.syn]), or
    • a discarded-value expression,

    then the result of the operation is an indeterminate value.

  • If an indeterminate value of unsigned ordinary character type or std​::​byte type is produced by the evaluation of the right operand of a simple assignment operator ([expr.ass]) whose first operand is an lvalue of unsigned ordinary character type or std​::​byte type, an indeterminate value replaces the value of the object referred to by the left operand.

  • If an indeterminate value of unsigned ordinary character type is produced by the evaluation of the initialization expression when initializing an object of unsigned ordinary character type, that object is initialized to an indeterminate value. If an indeterminate value of unsigned ordinary character type or std​::​byte type is produced by the evaluation of the initialization expression when initializing an object of std​::​byte type, that object is initialized to an indeterminate value.

None 个例外情况适用于您的示例对象,因此 UB 适用。


with memcpy is UB?

不是。 std::memcpy 将对象解释为字节数组,在这种特殊情况下没有 UB。如果您尝试阅读不确定的副本,您仍然有 UB(除非上述例外情况适用)。


why?

C++ 标准不包含大多数规则的基本原理。该特定规则自第一个标准以来就存在。它比有关陷阱表示的相关 C 规则稍微严格一些。据我了解,陷阱处理没有既定的约定,作者不希望通过指定来限制实现,而是选择将其指定为 UB。这也具有允许优化器推断永远不会读取不确定值的效果。


I might want to move this object into a container:

将未初始化的对象移动到容器中通常是逻辑错误。目前还不清楚您为什么要这样做。

C++ 标准的设计深受 C 标准的影响,C 标准的作者(根据已发布的基本原理)打算并期望实现将在实现质量的基础上扩展语言的语义通过在显然这样做有用的情况下有意义地处理程序,即使标准没有“正式”定义这些程序的行为。因此,这两个标准都更加重视确保它们不会强制执行可能会使某些实现变得不那么有用的行为,而不是确保它们强制执行应由高质量通用实现支持的所有内容。

在许多情况下,通过保证在任何有效存储区域上使用 memcpy 将在最坏的情况下以与用一些可能没有意义的位模式填充目的地,没有外部副作用,并且很少有让它做其他事情更容易或更有用的地方。任何人都应该关心 memcpy 的行为是否在涉及有效存储区域的特定情况下定义的唯一情况是某些替代行为确实比普通行为更有用的情况。如果存在这种情况,编译器编写者及其客户将比委员会更能判断哪种行为最有用。

作为替代行为可能更有用的情况的示例,请考虑使用 memcpy 复制部分写入的结构,然后使用它制作该结构的两个副本的代码。在某些情况下,让编译器只写入两个目标结构中已写入原始结构的部分可能会提高效率,但这种行为明显不同于第一个 memcpy 表现得好像它存储了一些位图案到目的地。请注意,如果没有以影响行为的方式使用结构的未初始化部分的副本,则此类更改不会对程序的整体行为产生不利影响,但标准没有很好的方法来区分可能发生或不可能发生的场景在这样的模块下,因此所有这些场景都未定义。