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

问题描述 投票:18回答:2

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

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

6.3.2.1

  1. 如果左值指定了一个自动存储持续时间的对象,该对象可以使用寄存器存储类声明(从未使用其地址),并且该对象未初始化(未使用初始化程序声明,并且在使用之前未对其进行任何赋值) ),行为未定义。

请注意,它具体说明并且没有对其进行任何分配。

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

6.2.6.1.

  1. 结构或联合对象的值永远不是陷阱表示,即使结构或联合对象的成员的值可能是陷阱表示

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

声明:定义了返回一个未初始化的结构,该结构的一个成员被赋予了有效值。

更容易理解的示例:

struct test
{
    int a;
    int b;
};

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

{
    struct test t = Get();
}

我碰巧专注于回归,但我相信这也适用于简单的任务,没有任何区别。

我的陈述是否正确?

c struct language-lawyer undefined-behavior c11
2个回答
10
投票

除了从函数返回值的细节之外,这恰好是2000年由Clive Feather提交的Defect Report 222的主题,并且该DR的解决方案似乎非常清楚地回答了这个问题:返回部分未初始化的struct是 - 已定义(尽管未使用未初始化成员的值。)

DR的决议澄清了structunion对象没有陷阱表示(已明确添加到§6.2.6.1/ 6)。因此,成员复制不能用于个别成员可能陷阱的体系结构。虽然,大概是为了简约,但没有在标准中添加明确的声明,脚注42(现在的脚注51),前面提到过逐个成员复制的可能性被一个更弱的声明所取代,表明填充比特不需要被复制。

minutes of the WG14 meeting (Toronto, October 2000)很清楚(重点补充):

DR222 - 部分初始化结构

这个DR问的问题是,当赋值的来源是struct时,struct赋值是否被很好地定义,其中一些成员没有给出值。由于共同使用,包括标准指定的结构struct tm,人们一致认为应该明确定义。还有一种共识是,如果对某些成员的未初始化(因此可能具有陷阱价值)进行明确界定,则要求至少有一名成员获得适当的价值几乎没有价值。 因此,正在消除structunion作为一个整体的价值可能具有陷阱值的概念。

值得注意的是,在上述会议纪要中,委员会认为,struct的一名成员甚至没有必要获得价值。但是,在某些情况下,该要求后来得到了恢复,解决了DR338(见下文)。

综上所述:

  • 如果自动聚合对象至少已部分初始化或者其地址已被采用(从而使其不适用于符合§6.3.2.1/ 2的register声明),则该对象的左值到右值转换很好-defined。
  • 可以在从函数返回之后将这样的对象分配给相同类型的另一个聚合对象,而不调用未定义的行为。
  • 读取副本中未初始化的成员要么是未定义的要么是不确定的,具体取决于陷阱表示是否可行。 (例如,通过指向无符号窄字符类型的指针读取不能捕获。)但是如果您在读取它之前编写成员,那么就没问题了。

我不相信unionstruct对象的分配有任何理论上的区别。显然unions不能被成员成员复制(甚至意味着什么),并且一些非活动成员碰巧有陷阱表示的事实是无关紧要的,即使该成员没有被任何其他元素别名化。没有明显的原因,为什么struct应该是任何不同的。

最后,关于§6.3.2.1/ 2中的例外情况:这是因为对DR 338的决议而增加的。该DR的要点是某些硬件(IA64)可以捕获在寄存器中使用未初始化的值。 C99不允许对无符号字符进行陷阱表示。因此,在这样的硬件上,可能无法在寄存器中维护自动变量而不“不必要地”初始化寄存器。

DR 338的解决方案特别标记为未定义的行为,在自动变量中使用未初始化的值,可以想象存储在寄存器中(即,那些地址从未被采用过,如同声明的register),从而允许编译器保持自动unsigned char在寄存器中,不用担心该寄存器的先前内容。

作为DR 338的副作用,似乎完全未初始化的自动structs,其地址从未被采用,不能进行左值到右值的转换。我不知道在DR 338的决议中是否完全考虑了这种副作用,但它不适用于部分初始化的struct,如本问题所示。


2
投票

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

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

然而,存在另一种未定义行为的情况,即将陷阱表示存储到左值中时:

6.2.6.1/5 某些对象表示不需要表示对象类型的值。如果对象的存储值具有这样的表示并且由不具有字符类型的左值表达式读取,则行为是未定义的。如果这样的表示是由副作用产生的,该副作用通过不具有字符类型的左值表达式修改对象的全部或任何部分,则行为是未定义的.50)这种表示称为陷阱表示。

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

但请注意“可能是陷阱”。不确定它们是陷阱表示,因为它们具有不确定的值。看看基础知识:

6.7.9 / 10 如果未显式初始化具有自动存储持续时间的对象,则其值不确定。

3.19.2 / 1 不确定的价值 要么是未指定的值,要么是陷阱表示

如果值是陷阱表示,则使用具有不确定值的变量仅是未定义的行为。

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

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

结论:如果实现声明任何struct成员的任何不确定值都是陷阱表示,则行为是未定义的。否则,行为仅仅是实现定义/未指定,未初始化的成员将保留未指定的值。

© www.soinside.com 2019 - 2024. All rights reserved.