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

时间:2016-02-18 20:44:52

标签: c struct language-lawyer undefined-behavior c11

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

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

  

6.3.2.1

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

请注意,它明确指出并且未对其进行任何分配

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

  

6.2.6.1。

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

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

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

更容易理解的例子:

struct test
{
    int a;
    int b;
};

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

{
    struct test t = Get();
}

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

我的声明是否正确?

2 个答案:

答案 0 :(得分:10)

除了从函数返回值的细节之外,这正是Clive Feather于2000年提交的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声明),那么左值该对象的左右转换是明确定义的。

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

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

我认为unionstruct对象的分配之间没有任何理论差异。显然,union不能逐个成员复制(这甚至意味着什么),并且一些非活动成员碰巧有陷阱表示的事实是无关紧要的,即使该成员没有任何别名其他元素struct应该有什么不同,没有明显的理由。

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

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

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

答案 1 :(得分:2)

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

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

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

  

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

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

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

  

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

  

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

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

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

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

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