在C中,使用未初始化的变量是未定义的行为。包括:
int x;int y = x;
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
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
在某些元素(而不是全部)被赋值的情况下,上面的代码是否会调用未定义的行为?
6qftjkof1#
C标准没有充分解决这个问题,但合理的C实现支持在某些成员未初始化时使用整个结构。对于任何C实现都有一个简单的解决方案。请注意,“在C中,使用未初始化的变量是未定义的行为”并不完全正确。根据C 2018 6.7.9 10,具有自动存储持续时间的未初始化对象的值为 * 不确定 *。根据C 2018 www.example.com 2,使用这样一个对象的值具有未定义的行为如果其地址未被采用6.3.2.1:.如果左值指定了一个可以用register存储类声明的自动存储持续时间的对象(从未获取其地址),并且该对象未初始化(未使用初始化器声明,并且在使用之前没有对其进行赋值),则行为未定义。因此,为了确保使用结构不会有未定义的行为,只需获取其地址。假设你有一个结构对象s。然后我们可以简单地通过在代码中放入(void) &s;来获取它的地址(并丢弃它)。通过获取它的地址,我们可以防止对象被声明为register,因此6.3.2.1 2将不适用。仍然可能存在一些问题,即使用具有不确定值的对象可能会遇到陷阱表示。但是,根据www.example.com 2,对于结构,不会发生这种情况6.2.6.1:结构或联合对象的值永远不是陷阱表示,即使结构或联合对象的成员的值可能是陷阱表示。这段话告诉我们,C委员会希望C程序能够使用整个结构,即使它们的成员没有全部初始化。6.3.2.1 2,关于某些未初始化对象的未定义行为,是后来添加的,以提供允许处理器跟踪未初始化寄存器的Hewlett-Packard功能。当添加时,委员会没有添加类似于www.example.com 2的声明6.2.6.1,告诉我们它不适用于结构。我的观点是,6.2.6.1 2表明其目的是为了安全地复制整个结构,6.3.2.1 2并不打算改变这一点。然而,为了完全安全,采用如上所述的结构的地址确保访问结构将不会具有未定义的行为,而不管人们在这方面对标准的解释如何。
register
s
(void) &s;
zujrkrfu2#
问题指出,
未定义的行为。那是不对的x有一个“不确定的值”,但这与未定义的行为不同。该标准允许陷阱表示,但在主流处理器上,int非常罕见,所以让我们忘记陷阱。在这种情况下,y将获得“某个值”并保留它,而访问x可能会在每次访问时产生(根据标准)不同的结果。同样的情况也适用于struct。没有未定义的行为。只是不确定的值。返回结构或赋值给另一个结构不会有任何区别。物体在那里...你只是不知道什么访问s.b将给你给予.
x
int
y
struct
s.b
8tntrjer3#
实际的问题和预期的问题似乎是不同的。对于实际的问题,简短的回答是否定的,它甚至不会编译-所以它根本不能创建任何未定义的行为。看来你想问的问题是:“返回一个部分赋值的结构会导致未定义的行为吗?“返回意味着这是一个函数的内部,所以也许这是一个不必要的复杂性,但让我们暂时接受它,并将op的原始代码 Package 在一个函数和一些额外的元素(如main)中进行讨论:
#include <stdio.h>struct Struct { int a; int b;};struct Struct myfunc(void){ struct Struct s; s.a = 1; struct Struct s0 = s; return s;}void main(void){ struct Struct bob = myfunc(); bob.b = 2; printf("bob.a --> %d, bob.b --> %d",bob.a, bob.b);}
#include <stdio.h>
struct Struct myfunc(void)
{
return s;
}
void main(void)
struct Struct bob = myfunc();
bob.b = 2;
printf("bob.a --> %d, bob.b --> %d",bob.a, bob.b);
首先,s到s0的第一个拷贝将导致一个拷贝,这意味着每个字段都被访问,这意味着这里有一个未定义的行为。假设这不会导致特定编译器崩溃,那么返回也是一个未定义的行为。在所有这些的基础上,假设没有崩溃,因为编译器无论如何都处理了它,bob.b = 2赋值使print语句的事情正常。所以如果你的编译器是错误代码的同谋,你可以逃脱未定义的行为。但是如果没有这个额外的赋值,在printf()调用中也会有未定义的访问行为,不管编译器是否同谋,这个问题最终都会变得很明显。当然,指向结构体的指针也经常被使用,这变得更加棘手。使用指针,你只是复制一个地址,所以在你访问字段之前,你不会有未定义的行为。当然,你还必须考虑深拷贝和浅拷贝,如果你把s作为该函数的局部变量,你将有其他未定义的行为,因为现在你将有一个非法的指针解引用,因为它指向堆栈,一旦你回到main,它就超出了范围。但是,如果你把这个结构体s放在全局作用域中,那么突然之间你就可以给字段b赋值了,而且完全没有问题,不会因为访问未初始化的变量而产生未定义的行为。当然,在全局作用域中你会有一堆乱七八糟的变量。
s0
bob.b = 2
printf()
main
b
qmb5sa224#
我不认为委员会曾经考虑过这个问题,也不认为成员们会就如何回答这个问题达成共识。该标准的一个弱点是,它的抽象模型不能有效地处理优化可能以与应用程序需求无关的方式影响程序行为的情况。例如:
struct blob { int count; char arr[32]; } x,y;void test1(void){ struct blob temp; temp.count = 2; temp.arr[0] = 1; temp.arr[1] = 2; x = temp; y = temp;}void test(void){ test1(); fwrite(&x, 1, sizeof (struct blob), someFile); fwrite(&y, 1, sizeof (struct blob), someFile);}
struct blob { int count; char arr[32]; } x,y;
void test1(void)
struct blob temp;
temp.count = 2;
temp.arr[0] = 1;
temp.arr[1] = 2;
x = temp;
y = temp;
void test(void)
test1();
fwrite(&x, 1, sizeof (struct blob), someFile);
fwrite(&y, 1, sizeof (struct blob), someFile);
如果宇宙中没有任何东西会关心写入文件的结构中的dat[2..31]的内容,那么处理代码的最有效方法就是让test简单地写入x和y的前导部分,剩下的部分保留调用之前的内容。我不认为委员会成员之间会达成共识,禁止这样的优化,但是如果不描述上面的test()函数,就没有办法允许它。另一方面,如果程序员保证程序行为的唯一方法是确保在将temp复制到x和y之前写入temp的所有部分,那么只是为了证明将test1()视为调用UB是合理的“优化”将变得毫无意义和无用,这意味着没有理由不将temp.arr的未写入部分视为包含Unspecified值(这将导致x.arr和y.arr包含相同的内容)。
test
test()
temp
test1()
temp.arr
x.arr
y.arr
4条答案
按热度按时间6qftjkof1#
C标准没有充分解决这个问题,但合理的C实现支持在某些成员未初始化时使用整个结构。对于任何C实现都有一个简单的解决方案。
请注意,“在C中,使用未初始化的变量是未定义的行为”并不完全正确。根据C 2018 6.7.9 10,具有自动存储持续时间的未初始化对象的值为 * 不确定 *。根据C 2018 www.example.com 2,使用这样一个对象的值具有未定义的行为如果其地址未被采用6.3.2.1:
.如果左值指定了一个可以用
register
存储类声明的自动存储持续时间的对象(从未获取其地址),并且该对象未初始化(未使用初始化器声明,并且在使用之前没有对其进行赋值),则行为未定义。因此,为了确保使用结构不会有未定义的行为,只需获取其地址。假设你有一个结构对象
s
。然后我们可以简单地通过在代码中放入(void) &s;
来获取它的地址(并丢弃它)。通过获取它的地址,我们可以防止对象被声明为register
,因此6.3.2.1 2将不适用。仍然可能存在一些问题,即使用具有不确定值的对象可能会遇到陷阱表示。但是,根据www.example.com 2,对于结构,不会发生这种情况6.2.6.1:
结构或联合对象的值永远不是陷阱表示,即使结构或联合对象的成员的值可能是陷阱表示。
这段话告诉我们,C委员会希望C程序能够使用整个结构,即使它们的成员没有全部初始化。6.3.2.1 2,关于某些未初始化对象的未定义行为,是后来添加的,以提供允许处理器跟踪未初始化寄存器的Hewlett-Packard功能。当添加时,委员会没有添加类似于www.example.com 2的声明6.2.6.1,告诉我们它不适用于结构。
我的观点是,6.2.6.1 2表明其目的是为了安全地复制整个结构,6.3.2.1 2并不打算改变这一点。然而,为了完全安全,采用如上所述的结构的地址确保访问结构将不会具有未定义的行为,而不管人们在这方面对标准的解释如何。
zujrkrfu2#
问题指出,
未定义的行为。
那是不对的
x
有一个“不确定的值”,但这与未定义的行为不同。该标准允许陷阱表示,但在主流处理器上,
int
非常罕见,所以让我们忘记陷阱。在这种情况下,
y
将获得“某个值”并保留它,而访问x
可能会在每次访问时产生(根据标准)不同的结果。同样的情况也适用于
struct
。没有未定义的行为。只是不确定的值。返回结构或赋值给另一个结构不会有任何区别。物体在那里...你只是不知道什么访问s.b
将给你给予.8tntrjer3#
实际的问题和预期的问题似乎是不同的。对于实际的问题,简短的回答是否定的,它甚至不会编译-所以它根本不能创建任何未定义的行为。
看来你想问的问题是:“返回一个部分赋值的结构会导致未定义的行为吗?“返回意味着这是一个函数的内部,所以也许这是一个不必要的复杂性,但让我们暂时接受它,并将op的原始代码 Package 在一个函数和一些额外的元素(如main)中进行讨论:
首先,
s
到s0
的第一个拷贝将导致一个拷贝,这意味着每个字段都被访问,这意味着这里有一个未定义的行为。假设这不会导致特定编译器崩溃,那么返回也是一个未定义的行为。在所有这些的基础上,假设没有崩溃,因为编译器无论如何都处理了它,
bob.b = 2
赋值使print语句的事情正常。所以如果你的编译器是错误代码的同谋,你可以逃脱未定义的行为。但是如果没有这个额外的赋值,在printf()
调用中也会有未定义的访问行为,不管编译器是否同谋,这个问题最终都会变得很明显。当然,指向结构体的指针也经常被使用,这变得更加棘手。使用指针,你只是复制一个地址,所以在你访问字段之前,你不会有未定义的行为。当然,你还必须考虑深拷贝和浅拷贝,如果你把
s
作为该函数的局部变量,你将有其他未定义的行为,因为现在你将有一个非法的指针解引用,因为它指向堆栈,一旦你回到main
,它就超出了范围。但是,如果你把这个结构体s
放在全局作用域中,那么突然之间你就可以给字段b
赋值了,而且完全没有问题,不会因为访问未初始化的变量而产生未定义的行为。当然,在全局作用域中你会有一堆乱七八糟的变量。
qmb5sa224#
我不认为委员会曾经考虑过这个问题,也不认为成员们会就如何回答这个问题达成共识。该标准的一个弱点是,它的抽象模型不能有效地处理优化可能以与应用程序需求无关的方式影响程序行为的情况。例如:
如果宇宙中没有任何东西会关心写入文件的结构中的dat[2..31]的内容,那么处理代码的最有效方法就是让
test
简单地写入x
和y
的前导部分,剩下的部分保留调用之前的内容。我不认为委员会成员之间会达成共识,禁止这样的优化,但是如果不描述上面的test()
函数,就没有办法允许它。另一方面,如果程序员保证程序行为的唯一方法是确保在将temp
复制到x
和y
之前写入temp
的所有部分,那么只是为了证明将test1()
视为调用UB是合理的“优化”将变得毫无意义和无用,这意味着没有理由不将temp.arr
的未写入部分视为包含Unspecified值(这将导致x.arr
和y.arr
包含相同的内容)。