如果 a 未初始化,a^a 或 a-a 是未定义的行为吗?
Posted
技术标签:
【中文标题】如果 a 未初始化,a^a 或 a-a 是未定义的行为吗?【英文标题】:Is a^a or a-a undefined behaviour if a is not initialized? 【发布时间】:2014-09-24 07:12:06 【问题描述】:考虑这个程序:
#include <stdio.h>
int main(void)
unsigned int a;
printf("%u %u\n", a^a, a-a);
return 0;
这是未定义的行为吗?
从表面上看,a
是一个未初始化的变量。所以这指向未定义的行为。但是对于a
的所有值,a^a
和a-a
等于0
,至少我认为是这样。是否有可能通过某种方式证明该行为是明确定义的?
【问题讨论】:
我希望这是明确定义的,因为 a 的值是未知的但固定的,它不应该改变。问题是编译器是否会为a
分配空间并随后从坐在那里的垃圾中读取。如果不是,则行为未定义。
嗯,只要变量没有被标记为volatile
,那么我会接受它作为定义的行为。 a ^= a
,完全等价于a = 0
@martin:这不是固定的。该值允许更改。这是一个非常实际的考虑。一个变量可以分配给一个 CPU 寄存器,但是当它未初始化时(即它的有效值生命周期尚未开始),同一个 CPU 寄存器可以被不同的变量占用。该其他变量的变化将被视为此未初始化变量的“不稳定”值。这是在使用未初始化变量的实践中经常观察到的。
@AndreyT 这是一个很好的解释
没关系,找到了,我的错误:***.com/questions/20300665/…,实际上是 C.
【参考方案1】:
在 C11 中:
如果a
的地址从未被占用(引用如下),则根据 6.3.2.1/2 明确未定义
它可能是一个陷阱表示(访问时会导致 UB)。 6.2.6.1/5:
某些对象表示不需要表示对象类型的值。
无符号整数可以有陷阱表示(例如,如果它有 15 个精度位和 1 个奇偶校验位,访问 a
可能会导致奇偶校验错误)。
6.2.4/6 表示初始值是indeterminate,3.19.2 下的定义是未指定的值或陷阱表示。 p>
进一步:在 C11 6.3.2.1/2 中,正如 Pascal Cuoq 所指出的:
如果左值指定了一个自动存储持续时间的对象, 使用寄存器存储类声明(从未占用其地址),以及该对象 未初始化(未使用初始化程序声明且未对其赋值 在使用前执行),行为未定义。
这对字符类型没有例外,所以这个子句似乎取代了前面的讨论;即使不存在陷阱表示,访问x
也会立即未定义。此子句was added to C11 支持实际上具有寄存器陷阱状态的 Itanium CPU。
没有陷阱表示的系统: 但是如果我们抛出 &x;
以使 6.3.2.1/2 的反对不再适用,并且我们处于一个已知没有陷阱的系统上怎么办?陈述?那么这个值就是一个未指定的值。
3.19.3中unspecified value的定义有点模糊,不过DR 451澄清了,总结如下:
在此分辨率下,int a; &a; int b = a - a;
导致 b
仍然具有不确定的值。
请注意,如果未将不确定值传递给库函数,我们仍处于未指定行为(不是未定义行为)的领域。结果可能很奇怪,例如if ( j != j ) foo();
可以叫 foo,但恶魔必须留在鼻腔中。
【讨论】:
假设我们知道没有陷阱值,那么我们可以争论定义的行为吗? @DavidHeffernan 您也可以将访问不确定数据视为 UB,因为即使没有陷阱值,您的编译器也可能会这样做。请参阅blog.frama-c.com/index.php?post/2013/03/13/… @Pascal 我现在明白了。这是安德烈回答的最后一段。 @DavidHeffernan 这些例子到了2 * j
很奇怪,这甚至比安德烈回答中的图片略差,但你明白了。
在编写 C89 标准时,预计实现会指定标准未指定的许多内容,标准的作者认为没有理由详细说明应考虑操作的所有情况在指定某些事物的实现上定义(例如,“unsigned int”没有陷阱表示的事实)但在没有的实现上未定义(例如,将不确定的位模式读取为“unsigned int”可能会产生陷阱表示)。 【参考方案2】:
是的,这是未定义的行为。
首先,任何未初始化的变量都可以有“破碎”(又名“陷阱”)表示。即使是一次访问该表示的尝试也会触发未定义的行为。此外,即使是非捕获类型的对象(如unsigned char
)仍然可以获得特殊的平台相关状态(如 NaT - Not-A-Thing - on Itanium),这可能表现为其“不确定值”的表现。
其次,未初始化的变量不能保证具有稳定值。对同一个未初始化变量的两次顺序访问可以读取完全不同的值,这就是为什么即使a - a
中的两次访问都“成功”(不是陷阱),仍然不能保证@987654326 @ 将评估为零。
【讨论】:
你有最后一段的引文吗?如果是这样,那么我们甚至不需要考虑陷阱。 @Matt McNabb:嗯,这可能是一个通过不同版本的语言规范以不同方式解决的问题。但是 DR#260 (open-std.org/jtc1/sc22/wg14/www/docs/dr_260.htm) 的决议明确指出,具有不确定值的变量可以“自行”任意更改。 @Matt McNabb:DR#451 在 2013 年 10 月和 2014 年 4 月open-std.org/Jtc1/sc22/WG14/www/docs/dr_451.htm 中重申了与 DR#260 基本相同的决定。委员会对 DR#451 的回应明确指出“这一观点重申了 C99 DR260 的立场” @hyde 最接近你手头的陷阱表示是信号 NaN。 en.wikipedia.org/wiki/NaN#Signaling_NaN 否则,您需要一台具有显式奇偶校验位的计算机,一台将 -0 视为陷阱值的符号幅度计算机,或同样具有异国情调的计算机。 @chux:不。没有什么可以限制 未定义的行为 为“按照您的想法做,但如果不是,则为陷阱”。从字面上看,任何行为都是允许的。【参考方案3】:如果一个对象具有自动存储持续时间并且其地址未被占用,则尝试读取它会产生未定义的行为。获取此类对象的地址并使用“unsigned char”类型的指针来读取其字节,标准保证会产生“unsigned char”类型的值,但并非所有编译器都在这方面遵守标准. ARM GCC 5.1,例如,当给定时:
#include <stdint.h>
#include <string.h>
struct q uint16_t x,y; ;
volatile uint16_t zz;
int32_t foo(uint32_t x, uint32_t y)
struct q temp1,temp2;
temp1.x = 3;
if (y & 1)
temp1.y = zz;
memmove(&temp2,&temp1,sizeof temp1);
return temp2.y;
如果 y 为零,将生成返回 x 的代码,即使 x 超出范围 0-65535。该标准明确规定,对 Indeterminate Value 的无符号字符读取保证产生unsigned char
范围内的值,memmove
的行为被定义为等同于字符读取和写入序列。因此,temp2 应该有一个可以通过字符写入序列存储到其中的值,但是 gcc 决定用赋值替换 memmove,并忽略代码获取 temp1 和 temp2 的地址这一事实。
有一种方法可以强制编译器将变量视为持有其类型的任意值,在任何此类值同样可以接受的情况下,这将是有帮助的,但标准没有指定一种干净的方法所以(除了存储一些可以工作的特定值,但通常是不必要的慢)。即使是在逻辑上应该强制变量保存一个可以表示为某种位组合的值的操作,也不能依赖于在所有编译器上工作。因此,这些变量无法保证任何有用的东西。
【讨论】:
公平地说,上面链接了一个缺陷报告,关于您可以对不确定值执行的确切 what 操作,部分决定是指定传递不确定值任何库函数都是UB。memmove
是一个库函数,因此适用于此。
@BeeOnRope:如果标准的作者已经包含了一种将不确定值解析为最坏未指定值的方法,那么在传递其他不确定值之前要求使用这种方法是合理的库函数的值。鉴于缺乏这样的手段,我能读懂他们的决定的唯一一点是,他们更感兴趣的是让一种语言“易于优化”,而不是最大化其有用性。
@BeeOnRope:他们的基本原理是,使行为未定义不应阻止编译器在针对处理器和应用程序领域定义行为时,这样做是可行和有用的。不幸的是,无论委员会的此类决定是否应该产生这样的效果,很明显它们确实如此。
我想,是的,他们本可以引入某种T std::freeze(T v)
方法,将“摇摆不定”的不确定值转变为未指定但稳定的值。但是它会有“三阶”的用处:使用不确定值已经很模糊而且很少使用,所以添加一个特殊的结构来巩固这些值似乎只是在已经是一个晦涩的角落的兔子洞中走得更远。该标准必须在许多编译器的核心转换/优化阶段得到支持。
@BeeOnRope:在必要的情况下,冻结值的能力基本上为零成本,并且在没有它的情况下尝试调试优化的代码肯定会导致精神错乱。如果一个人写的foo=moo; if (foo < 100) bar(foo);
和moo
被其他线程意外更改,试图诊断出问题的时间和地点可能基本上是不可能的。能够说出foo=moo; freeze(foo); if (foo < 100) bar(foo);
并让编译器提交foo
的值将使事情变得更加健壮。以上是关于如果 a 未初始化,a^a 或 a-a 是未定义的行为吗?的主要内容,如果未能解决你的问题,请参考以下文章
为啥我的 Redux reducer 认为我的状态是未定义的?
Swift UITest - 将使用两者之一。哪个是未定义的
Symfony 2,未定义的变量,在构造函数中初始化为 ArrayCollection 的受保护成员通过错误,它是未定义的