在这个 C90 未定义的行为定义中,“有符号或无符号类型”是啥意思?
Posted
技术标签:
【中文标题】在这个 C90 未定义的行为定义中,“有符号或无符号类型”是啥意思?【英文标题】:How is "signed or unsigned type" meant in this C90 undefined behaviour definition?在这个 C90 未定义的行为定义中,“有符号或无符号类型”是什么意思? 【发布时间】:2019-02-28 03:17:46 【问题描述】:在 ANSI C90 标准中,第 6.3 节对表达式有这样的说法:
一个对象的存储值只能由具有以下类型之一的左值访问:[...] 一个类型是 对应于声明类型的限定版本的有符号或无符号类型 对象的
附录 G.2 中有这种未定义行为的实例:
以下情况下的行为是未定义的:[...] 对象的存储值被不具有以下类型之一的左值访问:对象的声明类型,声明的限定版本对象的类型,对象声明类型对应的有符号或无符号类型,对象声明类型的限定版本对应的有符号或无符号类型,聚合或联合类型(递归地)在其成员中包含上述类型之一,或字符类型(6.3)。
我发现强调部分的措辞模棱两可,正在努力解释它。
是不是表示“有符号时对应原类型的有符号类型,无符号时对应原类型的无符号类型”;或“与原始类型相对应的类型(无论有符号还是无符号都无关紧要)”?即是:
signed int a = -10;
unsigned int b = *((unsigned int *) a);
...未定义?
如果有符号/无符号无关紧要,鉴于标准区分char
、signed char
和unsigned char
三种类型,将通过signed char *
或unsigned char *
被定义了吗?
【问题讨论】:
【参考方案1】:这是说将值转换为不同的符号是不是未定义的行为。如果对象声明为signed int
,则可以使用unsigned int
左值访问它,反之亦然。
当它说“对象的声明类型”时,已经涵盖了签名相同的情况,尽管这种情况也可以考虑这样说。
在char
的情况下,signed char
和unsigned char
都是该类型的“有符号或无符号类型”。
总而言之,它只是说左值的签名不影响访问是否定义良好。
【讨论】:
我对@987654326@ 是一个独特类型的评论有点含糊。我知道对于任何给定的实现,char
将是signed
或unsigned
版本;但是该标准确实在某些方面做出了区分,它不适用于其他整数类型,例如。 6.1.2.5中有“三种char
,signed char
,unsigned char
统称为字符类型。”不知道是不是这样虽然有任何实际意义;听起来好像没有。
char
是一个独特的类型。 C 2018 6.7.2 5 指定第 2 段列表中每一行中的元素指定相同的类型,除了它是实现定义的,对于位字段,int
是否与signed int
或unsigned int
的类型相同.该列表将char
、signed char
和unsigned char
放在不同的行中。 Note 45 部分地告诉我们,“char 是与其他两个不同的类型,并且与任何一个都不兼容。” Apple clang-1000.11.45.5 报告,当尝试为char *
返回signed char *
或unsigned char *
时,它们不兼容(两者)。
@EricPostpischil 谢谢,我已经编辑了答案,说在这种情况下它们都符合标准。【参考方案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
指向不同的对象。
(请注意,理论上非常奇特的系统可以为有符号类型提供填充位和陷阱表示,因此理论上通过 signed int*
访问 unsigned int
可能是 UB,因为其他原因。)
与其他整数类型相比,字符类型确实是一种特殊情况。但这并不重要,因为规则也有一个特殊情况:“或字符类型”。 char
、unsigned char
和 signed char
都是字符类型。这意味着使用这 3 种类型中的任何一种对左值的所有指针访问都是明确定义的。
左值类型甚至不需要是字符类型!例如,您可以通过signed char*
访问int
的左值,它是明确定义的,但反之则不行。
【讨论】:
在 C89/C90 中,每个对象都有一个类型。在C89/C90规则下,给定void *vp=calloc(4,128); float *fp = vp; int32_t *ip=vp;
,假设malloc
成功,fp[0]
、f[]1]
等将是float
和ip[0]
、ip[1]
等类型的对象int
类型的对象,而 vp
根本不会识别任何对象。 C89 的主要问题是“访问者”而不是“别名者”。通过这种更改,即使fp[0]
和ip[0]
同时存在,并且对fp[0]
的访问将访问ip[0]
的存储值,除非代码同时使用这两个值...
...ip[0]
和 fp[0]
来访问该对象,并且这些访问中至少有一个是写入。
关于 6.3 的好点,我已经把它作为我 Q 中的主要来源。
@supercat 我会更严格地阅读它,除非有另一个部分说明这一点? IE。代码可以同时声明fp
和ip
,但只有最多 fp[n]
或ip[n]
之一的代码才有效(写入或不写入)。这种解释错了吗?
@detly:C89 委员会被授权描述一种预先存在的语言;当int32_t
被定义为与float
相同大小的类型时,访问fp[1]
和ip[2]
的行为在该语言中被明确定义;已发布的基本原理中没有任何内容表明任何改变它的意图。委员会普遍认为,如果一项行动得到一致支持并且明显有用,他们不需要担心标准的规则是否真正定义了该行为,或者将其保留为“流行扩展”之一。理由。【参考方案3】:
在编写 C89 时,无符号类型是该语言的一个足够新的补充,许多代码在 unsigned
的地方使用 int
——一旦它存在——会更有意义。该标准的作者希望确保使用较新的unsigned
类型的函数能够与那些编写为使用int
的函数交换数据,因为unsigned
还不存在。
对于像unsigned*
这样的类型是否具有“对应的有符号类型”int*
,或者unsigned**
是否具有“对应的无符号类型”int**
等,标准有点模棱两可。鉴于目的允许在无符号类型之前的代码与使用它们的代码之间进行交互,使编写为在int*
序列上运行的函数无法被具有unsigned*
序列的客户端使用,这将违反该目的,也违反委员会的章程。维护既定目的不需要int**
可以普遍用于访问unsigned*
类型的对象,但需要编译器给出如下结构:
unsigned *foo[10];
actOnIntPtrs((int**)foo, 10);
认识到被调用的函数可能会影响存储在foo
中的unsigned*
类型的对象。
【讨论】:
以上是关于在这个 C90 未定义的行为定义中,“有符号或无符号类型”是啥意思?的主要内容,如果未能解决你的问题,请参考以下文章
C90:如何在没有 C99 扩展的情况下在 C 中全局初始化此结构
ISO C90 forbids mixed declarations and code 警告