SO上的各种受尊敬的高代表用户坚持认为阅读具有不确定价值的变量"总是UB"。那么在C标准中究竟提到了哪一个呢?
很明显,不确定的值可能是未指定的值或陷阱表示:
3.19.2
不确定的价值
要么是未指定的值,要么是陷阱表示3.19.3
未指明的价值
本国际标准规定的相关类型的有效值 在任何情况下选择哪个值的要求
注意未指定的值不能是陷阱表示。3.19.4
陷阱表示
一个对象表示,不需要表示对象类型的值
同样清楚的是,读取陷阱表示会调用未定义的行为,6.2.6.1:
某些对象表示不需要表示对象类型的值。如果存储 对象的值具有这样的表示,并由左值表达式读取 没有字符类型,行为是未定义的。如果产生这样的表示 通过副作用,通过左值表达式修改对象的全部或任何部分 没有字符类型,行为是未定义的.50)这样的表示被调用 陷阱表示。
但是,不确定值不一定包含陷阱表示。实际上,对于使用两个补码的系统来说,陷阱表示非常罕见。
在C标准中,它实际上是说读取不确定的值会调用未定义的行为吗?
我正在阅读C11的非规范性附件J,并发现这确实被列为UB的一个案例:
使用具有自动存储持续时间的对象的值 不确定(6.2.4,6.7.9,6.8)。
但是,列出的部分无关紧要。 6.2.4仅规定有关生命时间和变量值变得不确定的规则。类似地,6.7.9涉及初始化并说明变量的值如何变得不确定。 6.8似乎大多无关紧要。这些部分都没有包含任何规范性文本,说访问不确定的值可能导致UB。这是附件J中的缺陷吗?
然而,6.3.2.1中有关于左值的一些相关的规范性文本:
如果左值指定一个 可以用寄存器声明的自动存储持续时间的对象 存储类(从未使用过其地址),并且该对象未初始化(未声明) 使用初始化程序并且在使用之前没有执行任何操作),行为 未定义。
但这是一种特殊情况,它仅适用于从未使用过地址的自动存储持续时间的变量。我一直认为6.3.2.1的这一部分是UB关于不确定值(不是陷阱表示)的唯一情况。但是人们一直坚持认为,它始终是UB"。那么这究竟提到了什么?
答案 0 :(得分:0)
据我所知,标准中没有任何内容表明使用不确定的值始终是未定义的行为。
拼写为调用未定义行为的案例是:
例如,C标准指定类型unsigned char
没有填充位,因此它的值都不能成为陷阱表示。
memcpy
等函数的可移植实现利用这一事实来执行任何值的副本,包括不确定的值。当用作包含填充位的类型的值时,这些值可能是陷阱表示,但当用作unsigned char
的值时,它们只是未指定。
我认为假设如果可以调用未定义的行为,那么当程序没有安全的检查方式时,会调用未定义的行为是错误的。请考虑以下示例:
int read(int* array, int n, int i)
{
if (0 <= i)
if (i < n)
return array[i];
return 0;
}
在这种情况下,read
函数无法安全地检查array
是否确实属于(至少)长度n
。显然,如果编译器将这些可能的UB 操作视为明确的UB ,那么编写任何指针代码几乎是不可能的。
更一般地说,如果编译器无法证明某些东西是UB,则必须假设它不是UB,否则可能会破坏符合规范的程序。
唯一可能被视为确定性的情况是自动存储对象的情况。我认为合理的原因是因为这些情况可以被静态拒绝,因为编译器需要的所有信息都可以通过本地流分析获得。
另一方面,将其声明为非自动存储对象的UB不会在编译或可移植性方面为编译器提供任何有用的信息(在一般情况下)。因此,标准可能没有提到这些情况,因为无论如何它都不会改变实际实现中的任何内容。
答案 1 :(得分:0)
为了最佳地融合优化机会和有用的语义,没有陷阱表示的类型应该将Indeterminate Values细分为三种类型:
第一次读取将产生任何可能由未指定的值产生的值 位模式;后续将保证产生相同的价值。 除了标准之外,这类似于“未指定的值” 通常不区分有和没有陷阱的类型 表示,以及标准要求“未指定”的情况 价值“它要求实施确保价值不是陷阱 表示;在一般情况下,这将需要一个 实现包括防止某些位模式的代码。
每次阅读都可以独立产生任何可能导致的值 未指定的位模式。
读取的值,以及对其执行的大多数计算的结果, 可能表现得非确定性,就像读取产生了任何一样 可能的价值。
不幸的是,标准没有做出这样的区分,而且有一些区别 对它所要求的不同意见。我建议#2应该是 默认情况下,代码应该可以指示所有代码所在的位置 需要强制编译器选择一个具体的值,并指出一个 编译器可能在其他地方使用#3风格的语义。例如,如果代码为 存储为:
的不同16位值的集合struct COLLECTION { size_t count; uint16_t values[65536], locations[65536]; };
保持每个i&lt;的不变量。 count,locations [values [i]] == i,it 应该可以通过设置“计数”来初始化这样的结构 为零,即使存储先前已被用作其他类型。 如果将强制转换指定为始终产生具体值,则需要代码 看看集合中是否有东西可以使用:
uint32_t index = (uint32_t)(collection->locations[value]);
if (index < collection->count && collections->values[index]==value)
... value was found
每次从数组中读取一个项目时,上面的代码任意为“index”产生任意数字是可以接受的,但是第二行中“index”的两个使用都必须使用相同的值
不幸的是,一些编译器编写者似乎认为编译器应将所有不确定值视为#3,而某些算法需要#1而某些算法需要#2,并且没有真正的方法可以区分不同的需求。
答案 2 :(得分:0)
3.19.2允许实现成为陷阱表示,读取和写入都是未定义的行为。
您的平台可能会为您提供保证(例如,整数类型永远不会有陷阱表示),但标准不需要,如果您依赖它,您的代码将失去一些可移植性。这是一个有效的选择,但不应该无知。
更多系统具有浮点类型的陷阱表示而不是整数类型,但C程序可以在跟踪寄存器有效性的处理器上运行 - 请参阅(Why) is using an uninitialized variable undefined behavior in C?。这种程度的自由度是C在许多硬件架构中广泛采用的主要原因。