在ANSI C90标准中,第6.3节对表达式进行了说明:
对象只能由具有以下类型之一的左值访问其存储的值:[...]类型是带符号或无符号类型,与声明的类型的限定版本相对应< / strong>的对象
附件G.2中存在未定义行为的情况:
在以下情况下的行为是不确定的:[...]对象的存储值由不具有以下类型之一的左值访问:对象的声明类型,声明的限定版本对象的类型,与对象的声明类型相对应的带符号或无符号类型,与对象的声明类型的限定版本相对应的带符号或无符号类型, (递归)包括上述成员之一或字符类型(6.3)。
我发现强调部分的措词不明确,并且难以解释。
是否表示“如果已签名,则与原始类型相对应的已签名类型,或者,如果未签名,则与原始类型相对应的未签名类型”;或“与原始类型相对应的类型(无符号或无符号)”?即是:
signed int a = -10;
unsigned int b = *((unsigned int *) a);
...未定义?
如果带符号/无符号无关紧要,则考虑到该标准区分了char
,signed char
和unsigned char
这三种类型,则可以访问{{ 1}}是通过char
还是signed char *
来定义的?
答案 0 :(得分:4)
这是将值转换为不同符号的行为,这不是不是行为。如果将对象声明为signed int
,则可以使用unsigned int
左值访问它,反之亦然。
当它说“对象的声明类型”时,已经覆盖了签名相同的情况,尽管这种情况也可以认为是这样。
对于char
,signed char
和unsigned char
均为“对应于该类型的有符号或无符号类型”。
总而言之,就是说左值的有符号性不会影响访问是否定义良好。
答案 1 :(得分:2)
请注意,附件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
都是字符类型。这意味着使用这三种类型中的任何一种对左值的所有指针访问都是定义明确的。
左值类型甚至不必是字符类型!例如,您可以左值访问int
到signed char*
,并且它是定义明确的,但并非相反。
答案 2 :(得分:1)
编写C89时,无符号类型是对语言的一种新的添加,因此许多代码在int
(一旦存在)中使用unsigned
的地方就更有意义了。该标准的作者希望确保使用较新的unsigned
类型的函数能够与使用int
编写的函数交换数据,因为尚不存在unsigned
对于类似unsigned*
的类型是否具有“相应的有符号类型” int*
或unsigned**
具有“相应的无符号类型” {{1} }等。鉴于允许未签名类型之前的代码与使用它们的代码之间进行交互的目的,使得编写为对int**
序列进行操作的函数无法被具有int*
序列的客户端使用不符合该目的,也违反委员会的章程。坚持既定目的并不要求unsigned*
普遍可用于访问类型int**
的对象,而是要求编译器具有以下结构:
unsigned*
认识到被调用函数可能会影响存储在unsigned *foo[10];
actOnIntPtrs((int**)foo, 10);
中的unsigned*
类型的对象。