从函数和未定义行为返回局部部分初始化的结构

Returning a local partially initialized struct from a function and undefined behavior

(通过部分初始化我的意思是定义为未初始化并且它的一个成员设置为某个有效值,但不是全部。并且通过本地我的意思是定义为自动存储持续时间。这个问题只讨论那些。 )

使用可以用寄存器定义的自动未初始化变量,因为右值是未定义的行为。可以使用寄存器存储 class 说明符定义结构。

6.3.2.1

  1. If the lvalue designates an object of automatic storage duration that could have been declared with the register storage class (never had its address taken), and that object is uninitialized (not declared with an initializer and no assignment to it has been performed prior to use), the behavior is undefined.

请注意,它明确表示并且没有对其执行任何分配

此外,我们知道结构不能是陷阱值:

6.2.6.1.

  1. The value of a structure or union object is never a trap representation, even though the value of a member of the structure or union object may be a trap representation

因此返回一个未初始化的结构显然是未定义的行为。

声明:返回一个未初始化的结构,该结构的其中一个成员被分配了有效值,已定义。

为了更容易理解的示例:

struct test
{
    int a;
    int b;
};

struct test Get( void )
{
    struct test g;
    g.a = 123;
    return g;
}

{
    struct test t = Get();
}

我只是碰巧专注于返回,但我相信这应该也适用于简单的作业,没有任何区别。

我的陈述正确吗?

您关于 6.3.2.1 的说法是正确的,如果分配给左值的对象未初始化,则行为未定义。

所以接下来的问题是你的结构是否被视为未初始化。您确实为其中一个成员分配了一个值,因此已经为该对象分配了一个值。根据引用的 6.3.2.1,这意味着您不能将结构视为未初始化的整体。该特定成员显然已初始化,即使其他成员未初始化。

然而还有另一种未定义行为的情况,那就是将陷阱表示存储到左值时:

6.2.6.1/5
Certain object representations need not represent a value of the object type. If the stored value of an object has such a representation and is read by an lvalue expression that does not have character type, the behavior is undefined. If such a representation is produced by a side effect that modifies all or any part of the object by an lvalue expression that does not have character type, the behavior is undefined.50) Such a representation is called a trap representation.

您在 6.2.6.1/6 中引用的文字说结构本身不能是陷阱表示,即使它的各个成员可能是陷阱表示。如果是,则分配将是上述未定义的行为。

但请注意"may be trap"。不确定它们是否是陷阱表示,因为它们具有 不确定值。看看基础知识:

6.7.9/10
If an object that has automatic storage duration is not initialized explicitly, its value is indeterminate.

3.19.2/1
indeterminate value
either an unspecified value or a trap representation

使用具有不确定值的变量只是在值是陷阱表示的情况下的未定义行为。

您的结构的未初始化成员变量是否包含未指定的值或陷阱表示是实现定义的行为。

如果具有不确定值的变量只是具有未指定的值,则 6.2.6.1/5 不适用并且没有未定义的行为。

结论:如果实现声明任何结构成员的任何不确定值都是陷阱表示,则行为未定义。否则,行为仅仅是 implementation-defined/unspecified,未初始化的成员将持有未指定的值。

除了从函数返回值的细节外,这正是 Clive Feather 在 2000 年提交的 Defect Report 222 的主题,该 DR 的解决方案似乎非常清楚地回答了这个问题:返回一个 partially-uninitialized struct 是 well-defined (尽管可能不会使用未初始化成员的值。)

DR 的决议阐明 structunion object 没有陷阱表示(已明确添加到 §6.2.6.1/6)。因此,member-by-member 复制不能用于单个成员可能陷入困境的体系结构。虽然,大概是为了节俭,没有在标准中添加关于这种效果的明确声明,但之前提到 member-by-member 复制的可能性的脚注 42(现在是脚注 51)被一个更弱的声明所取代,表明填充位不需要被复制。

minutes of the WG14 meeting (Toronto, October 2000) 清楚(强调):

DR222 - Partially-initialized structures

This DR asks the question of whether or not struct assignment is well defined when the source of the assignment is a struct, some of whose members have not been given a value. There was consensus that this should be well defined because of common usage, including the standard-specified structure struct tm. There was also consensus that if assignment with some members uninitialized (and thus possibly having a trap value) was being made well defined, there was little value in requiring that at least one member had been properly given a value.
Therefore the notion that the value of a struct or union as a whole can have a trap value is being removed.

值得注意的是,在上述会议记录中,委员会认为甚至没有必要为 struct 的单个成员赋予价值。但是,后来在某些情况下恢复了该要求,并通过了 DR338 决议(见下文)。

总结:

  • 如果自动聚合 object 至少已部分初始化或者其地址已被占用(因此根据 §6.3 使其不适合 register 声明.2.1/2),那么 object 的 lvalue-to-rvalue 转换为 well-defined。

  • 这样的 object 可以分配给另一个相同类型的聚合 object,可能是在从函数返回之后,而不调用未定义的行为。

  • 读取副本中未初始化的成员是未定义的或不确定的,具体取决于陷阱表示是否可能。 (例如,通过指向无符号窄字符类型的指针进行读取不会陷入陷阱。)但是,如果您在读取之前写入该成员,则没有问题。

我认为 unionstruct object 的赋值在理论上没有任何区别。显然 unions 不能逐个成员复制(这甚至意味着什么),并且某些非活动成员恰好具有陷阱表示这一事实是无关紧要的,即使该成员没有别名任何其他元素。没有明显的理由说明为什么 struct 应该有所不同。

最后,关于 §6.3.2.1/2 中的例外情况:这是根据 DR 338 的决议添加的。该 DR 的要点是某些硬件 (IA64) 可以捕获在寄存器 中使用未初始化值 。 C99 不允许无符号字符的陷阱表示。所以在这样的硬件上,如果不 "unnecessarily" 初始化寄存器,可能无法在寄存器中维护自动变量。

DR 338 的决议特别将在自动变量中使用未初始化值的行为标记为未定义行为,这些值可以想象存储在寄存器中(即那些地址从未被占用的值,就好像已声明 register) ,从而允许编译器在寄存器中保留一个自动 unsigned char,而不用担心该寄存器的先前内容。

作为 DR 338 的副作用,似乎完全未初始化的自动 struct 地址从未被占用的地址无法进行 lvalue-to-rvalue 转换。我不知道 side-effect 是否在 DR 338 的决议中得到了充分考虑,但它不适用于部分初始化 struct 的情况,如这个问题。