在ANSI C90标准中,第6.3节有关于表达式的说法:
对象的存储值只能由具有以下类型之一的左值访问:[...]一种类型,它是有符号或无符号类型,对应于声明的对象类型的限定版本
附件G.2中存在未定义行为的这种情况:
以下情况中的行为是未定义的:[...]对象的存储值由左值访问,左值不具有以下类型之一:对象的声明类型,声明的类型的限定版本object,对应于声明的对象类型的signed或unsigned类型,对应于声明的对象类型的限定版本的signed或unsigned类型,(递归地)包括上述类型之一的聚合或联合类型其成员或字符类型(6.3)。
我发现强调部分的措辞含糊不清,并且正在努力解释它。
signed int a = -10;
unsigned int b = *((unsigned int *) a);
...不确定?char
,signed char
和unsigned char
这三种类型,是否可以通过char
或signed char *
访问unsigned char *
?它说,将值转换为不同的签名并不是未定义的行为。如果对象声明为signed int
,则可以使用unsigned int
左值访问它,反之亦然。
当签名是“声明的对象类型”时,已经涵盖了签名相同的情况,尽管这种情况也可以被认为是这样。
在char
的情况下,signed char
和unsigned char
都是“对应于该类型的有符号或无符号类型”。
总而言之,它只是说左值的符号性不会影响访问是否定义明确。
请注意,附件G是资料性的,引用的相关部分是规范性C90 6.3。
这是指后来在C99中引入的“严格混叠规则”的前身。在C90中,如何处理没有类型的对象,例如malloc
返回的数据,这是不明确的。
signed int
或unsigned int
,您可以使用signed int*
或unsigned int*
进行左值访问。允许这两种指针类型别名。例如,如果你有这样的函数:
void func (signed int* a, unsigned int* b)
然后编译器不能假设a
和b
指向不同的对象。
(请注意,奇异异常的系统理论上可以为带符号类型提供填充位和陷阱表示,因此在理论上,由于其他原因,通过unsigned int
访问signed int*
可能是UB。)char
,unsigned char
和signed char
都是角色类型。这意味着使用这3种类型中的任何一种对所有对L值的指针都是明确定义的。
左值类型甚至不需要是字符类型!例如,你可以通过int
左右访问signed char*
,它是明确定义的,但不是相反。当编写C89时,无符号类型是语言的一个新的补充,许多代码在int
(曾经存在)的地方使用unsigned
会更有意义。该标准的作者希望确保使用较新的unsigned
类型的函数能够与那些使用int
编写的函数交换数据,因为unsigned
尚不存在。
关于像unsigned*
这样的类型是否具有“对应的带符号类型”int*
,或者unsigned**
是否具有“相应的无符号类型”int**
等,标准有点模糊。鉴于允许在无符号类型之前的代码之间进行交互的目的与使用它们,使用int*
序列的unsigned*
序列编写的函数将与int**
的序列相反,这将与委员会的章程相悖。坚持所述目的并不要求unsigned*
可以普遍用于访问unsigned *foo[10];
actOnIntPtrs((int**)foo, 10);
类型的对象,但是要求编译器给出如下构造:
unsigned*
认识到被调用的函数可能会影响存储在foo
中的qazxswpoi类型的对象。