无符号整型指针和有符号整型指针的区别

28

是否存在与int*不同的unsigned int*?我知道unsigned类型具有更高的值范围。但是,int*难道不能指向任何unsigned int吗?


4
指针指向比特(bit)。指针的类型告诉你引用了多少位(bit)和它们的“含义”。没有这些信息,这些比特就是无意义的。 - Hot Licks
@HotLicks:指针指向一个特定类型的对象。 - Keith Thompson
2
@KeithThompson - 指针指定类型。也许是正确的方式,也可能不是。 - Hot Licks
2
指针的“类型”告诉编译器,当您取消引用它或进行指针数学运算时,在该地址处期望什么类型的数据。但实际上,它只是一个地址而已。毕竟,除非您告诉编译器,否则它无法知道某个随机地址处的随机位表示什么含义。 - Lee Daniel Crocker
很有趣的是,有3个答案比被采纳的答案评分更高。了解原帖作者对选择的想法会很有趣。 - chux - Reinstate Monica
6个回答

20

int *unsigned int *是两种不兼容的指针类型,它们也分别是指向不兼容类型的指针。关于"兼容类型"的定义,请参考C标准(C11)中的§ 6.2.7

指向不兼容类型的指针意味着例如这样的情况:

unsigned int a = 42;

int *p = &a;  // &a is of type unsigned int *

无效(违反赋值运算符的限制约束)。

这两种类型之间的另一个差异是,与大多数其他指针类型一样(尽管在这里不太可能),C语言不能保证它们具有相同的大小或相同的表示形式。


5
哦。问题是“是否存在与int *不同的unsigned int *”,给我一个反对者在我的回答中有什么错误。 - ouah
6
我认为这个回答没有任何问题。此外,如果你不敢发表评论,请不要给负面评价。 - haccks
3
从 C 语言的角度来看,@Namfuak 的语句是无效的。赋值运算符的限制条件被违反了。需要进行诊断,并且编译器可以拒绝翻译该程序。 - ouah
7
这段代码存在约束违规,但是 int *p = (int *)&a 是有效的。您的回答只解决了编译时是否需要强制转换的问题,然而我认为OP询问的是如果包括强制转换,运行时会发生什么。 - M.M
3
这句话的意思是:“这直接与SE自己的建议相矛盾。在给出少于2k声望值的评论负评时,会显示信息‘如果你认为该帖子可以改善,请考虑添加评论’。” - user820304
显示剩余6条评论

18

使用无符号指针指向有符号类型的相同类型是由C标准定义的。

因此,通过无符号int指针解释int类型数据以及通过有符号int指针解释unsigned int类型数据都是有效的。

ISO/IEC 9899:201x 6.5 表达式, p7:

一个对象的存储值只能通过具有以下类型之一的lvalue表达式访问: 88)

— 对象的有效类型对应的已签名或未签名类型

— 对象的有效类型的已修饰版本对应的已签名或未签名类型

88) 此列表的目的是指定对象可能或不可能别名的情况。

有效类型基本上是对象的类型:

对于访问其存储值的对象,其有效类型是对象的已声明类型(如果存在)。


关于上述规则的解释存在争议。以下是我对此的额外解释。

下面的文本仅用于解释“corresponding”一词的语义,而不是直接规定的规则。

6.2.5 类型

p6:对于每个有符号整数类型,都有一个相应的(但不同)无符号整数类型(用关键字unsigned指定),它使用相同的存储空间(包括符号信息)并具有相同的对齐要求。

p9:有符号整数类型的非负值范围是相应的无符号整数类型的子范围,并且在每种类型中表示相同的值。41)

p12:对于每种浮点类型,都有一个相应的实数类型,它始终是实浮点类型的一种。

对于实数浮点类型,它是相同的类型。对于复杂类型,它是通过从类型名称中删除关键字“_Complex”得到的类型。

p27: 此外,还有_Atomic限定符。_Atomic限定符的存在指定了原子类型。原子类型的大小、表示和对齐方式不一定与相应的非限定类型相同。

6.2.6.2 整数类型

p2: 对于有符号整数类型,对象表示的位应分为三组:值位、填充位和符号位。不需要有任何填充位;signed char不得有任何填充位。应该恰好有一个符号位。每个值位上的位应与相应无符号类型的对象表示中的相同位具有相同的值。

p5: 任何填充位的值都是未指定的。54)当符号位为零的有符号整数类型的有效(非陷阱)对象表示是相应无符号类型的有效对象表示,并且应表示相同的值。

(还有很多使用单词corresponding的相同例子)

如您在上面的片段中所看到的,标准使用单词“ corresponding”来引用具有不同类型或具有不同修饰符/限定符的类型。因此,正如上面的例子中所见,标准使用单词,就像在此示例中使用的单词一样:限定类型对应于类型。

突然将单词“ corresponding”用于不同的目的,即引用完全相同的限定/规定类型,甚至无谓地将有符号和无符号的单词放在同一句话中,是不合逻辑的。

6.5、p7 的意图是:一种有符号或无符号类型,要么是与对象的有效类型相对应的有符号或无符号类型,要么与目标类型匹配(相对应)。例如:有效类型是 int,则 int 或 unsigned int 对应于该类型。


1
你的意思是 int *unsigned int * 可以互换使用而不需要强制转换吗? - Gamal Othman

16

unsigned int *int *是不同的类型。 要将其中一个转换为另一个,必须使用强制转换。

如果您通过指针读取值,则尝试将存储在该内存位置上的位解释为指针正在读取的类型所指向的类型的位。

如果该内存位置上的位不是由与您正在读取的指针相同类型的指针编写的,则称此为别名

严格别名规则指定哪些类型可能或不可能具有别名; 类型的signedunsigned版本之间始终允许别名。

但是,如果这些位不是类型中值的有效表示,则会导致未定义的行为。

在现代系统中,没有这样的“陷阱”表示,因此您不会遇到问题。 但假设您在一个负零陷阱的1的补码系统上:

unsigned int x = 0xFFFFFFFF;
int *y = (int *)&x;
printf("%d\n", y);

试图读取y可能会导致硬件故障或任何其他行为。


所以显然,gcc和clang允许这种分配而不需要转换为扩展,尽管我无法说扩展背后的原因是好是坏。 - Shafik Yaghmour
@ShafikYaghmour 他们打印出一份诊断报告,因此符合标准;问题在于此诊断默认为“警告”而不是“错误”。 - M.M
我承认在编程过程中会利用编译器扩展,将char *unsigned char *交替使用,这样做虽然方便处理二进制和文本混合缓存的数据,但也让人感到有些烦恼。 - M.M

6
指针的值是一样的,但它们是不同的类型。取决于您如何解释指针(例如:解引用),差异将会出现。
unsigned int *u;
int *d;
unsigned int v = 2147483648; /* 2^31 */
u = &v;
d = (int*) &v;
printf("%u\n", *u);
printf("%d\n", *d);

将输出:

2147483648
-2147483648

输出结果的差异是因为在 printf("%d\n", *d) 中,d 被解引用并打印,就好像它指向一个 signed int 一样,但实际上并不是。因此,您需要在代码中区分这两种指针。


4
这里不允许使用u = d = &v;。这违反了赋值运算符的限制条件。你需要进行显式转换(即强制类型转换)。 - ouah
6
在C11标准中,6.7.6.1p2规定:“为了使两个指针类型相容,它们必须具有相同的限定符并且都是指向兼容类型的指针。”而6.2.7p1规定:“如果两个类型相同,则它们具有兼容类型。[...]” - ouah
@ouah 我已经在我的回答中做出了修正。 - Arjun Sreedharan
指针的值是否相同是由标准保证的,还是只是一种实际观察? - martinkunev

2

它可以指向两个相同大小的值。问题在于,这将引入难以发现的错误,因为您将会将有符号的值解释为无符号的值或反之亦然。


2

指针是一个内存地址的数字。因此,指针必须具有足够的精度,以便能够寻址所有实现的内存。

无论您引用有符号还是无符号整数,在指针的内部结构上都没有区别,因为理论上,int或unsigned int几乎可以在内存中的任何位置。数据类型(unsigned)必须声明为“帮助”编译器决定代码的正确性。


5
就现实而言,位(bits)就是位。 - Lee Daniel Crocker
6
“玫瑰依然是玫瑰。” 如果只看碎片,就没有“指针”的概念。 “指针”这个词只有在C语境下才有意义;在那个语境中,它不是一个数字。 指针可以转换为整数类型或反之,但这种转换可能并不简单。 - Keith Thompson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接