在 C 中,使用未初始化的变量是未定义的行为。包括:
int x;
int y = x;
但是,假设我有以下内容:
struct Struct {
int a;
int b;
};
struct Struct s;
s.a = 1;
struct Struct s0 = s;
return s; // Assuming an enclosing function with the correct return type
如果某些元素(但不是全部)已被分配,上述代码是否会调用未定义的行为?
C 标准没有充分解决这个问题,但合理的 C 实现支持在某些成员未初始化时使用整个结构。对于任何 C 实现都有一个简单的解决方法。
请注意,“在 C 语言中,使用未初始化的变量是未定义的行为”并不完全正确。根据 C 2018 6.7.9 10 ,具有自动存储持续时间的未初始化对象的值是不确定的。根据 C 2018 6.3.2.1 2,如果未获取其地址,则使用此类对象的值将具有未定义的行为:
因此,为了确保使用结构不会有未定义的行为,只需获取其地址即可。假设你有一些结构对象
s
。(void) &s;
然后我们只需输入代码即可获取它的地址(并丢弃它) 。通过获取其地址,我们可以防止该对象被声明register
,因此 6.3.2.1 2 将不适用。仍然可能有人担心使用具有不确定值的对象可能会遇到陷阱表示。然而,根据 6.2.6.1 2,这种情况不会发生在结构上:
该段落告诉我们,C 委员会希望 C 程序能够使用整个结构,即使其成员并未全部初始化。6.3.2.1 2、关于某些未初始化对象的未定义行为是后来添加的,以提供允许处理器跟踪未初始化寄存器的惠普功能。当添加该内容时,委员会没有添加类似于 6.2.6.1 2 的声明,告诉我们它不适用于结构。
我的观点是,6.2.6.1 2 表明其目的是为了安全地复制整个结构,而 6.3.2.1 2 无意改变这一点。然而,为了完全安全,如上所述获取结构的地址可确保访问该结构不会出现未定义的行为,无论人们对这方面的标准的解释如何。
实际问题和预期问题似乎不同。回答实际问题,简短的答案是否定的,它甚至不会编译 - 所以它根本不能创建任何未定义的行为。
看来预期的问题是:“返回部分分配的结构会导致未定义的行为吗?” 返回意味着这是在函数内部,所以这可能是一个不必要的复杂化,但让我们暂时考虑一下,并将操作的原始代码包装在一个函数和一些额外的元素(如 main )中以供讨论:
首先,
s
into的第一个副本s0
将导致一个副本,这意味着每个字段都被访问,这意味着这里有未定义的行为。假设这不会导致您的特定编译器崩溃,那么返回也是未定义的行为。在主要内容的下面,假设没有崩溃,因为编译器无论如何都处理了它,那么赋值
bob.b = 2
使 print 语句一切正常。因此,如果您的编译器参与了错误代码,您可以摆脱未定义的行为。但是,如果没有额外的分配,您在调用中的访问也会有未定义的行为printf()
,无论是否是同谋编译器,问题实际上最终都会变得明显。当然,也经常使用指向结构的指针,这会变得更加棘手......使用指针,您只是复制地址,因此在访问字段之前不会有未定义的行为。当然,您还必须考虑深复制与浅复制,如果您保留
s
作为该函数的局部变量,您将有其他未定义的行为,因为现在您将有一个非法的指针取消引用,因为它指向堆栈一旦您回到 . 中,就超出了范围main
。但是,如果您将该结构放在s
全局范围内,那么您突然可以分配字段b
并完全正常,而不会因访问未初始化的变量而出现未定义的行为。当然,然后你就会在全局范围内遇到混乱的变量......
问题指出
是未定义的行为。
那是错误的。具有
x
“不确定的值”,但这与未定义的行为不同。该标准允许陷阱表示,但这
int
在主流处理器上非常罕见,所以让我们忘记陷阱。在这种情况下
y
,将获得“某些值”并在访问时保留它,x
每次访问时可能会产生(根据标准)不同的结果。这同样适用于
struct
. 没有未定义的行为。只是不确定的值。返回结构或分配给另一个结构不会有任何区别。该对象就在那里......您只是不知道s.b
将给您带来什么访问权限。我认为委员会从未考虑过这个问题,也不认为成员们会对如何回答这个问题达成共识。该标准的一个弱点是它的抽象模型无法有效地处理优化可能以与应用程序需求无关的方式影响程序行为的情况。例如考虑:
如果宇宙中没有任何东西会关心写入文件的结构中 dat[2..31] 的内容,那么处理代码的最有效方法就是简单地编写 和 的前导部分
test
,留下其余的人则拿着通话前所持有的任何东西。我认为委员会成员不会就应该禁止这种优化达成共识,但如果不描述上述函数的特征,就不可能允许这种优化。另一方面,如果程序员保证程序行为的唯一方法是确保在将其复制到和之前写入所有部分,那么“优化”只是为了证明处理的合理性x
y
test()
temp
x
y
test1()
因为调用 UB 将变得毫无意义且无用,因此意味着没有理由不将 的未写入部分temp.arr
视为保存未指定的值(这将导致x.arr
并y.arr
保存相同的内容)。