当我看到这个例子时,我正在阅读有关size_t
和ptrdiff_t
数据类型here的使用情况的文章:
代码:
int A = -2;
unsigned B = 1;
int array[5] = { 1, 2, 3, 4, 5 };
int *ptr = array + 3;
ptr = ptr + (A + B); //Error
printf("%i\n", *ptr);
我无法理解一些事情。首先,如何添加signed
和unsigned
数字将输入结果转换为unsigned
类型?如果结果确实是0xFFFFFFFF
unsigned
类型的原因,那么为什么在32位系统中,在添加ptr
时,为什么会将其解释为ptr-1
实际上是unsigned
类型,前导1不应该表示符号吗?
第二,为什么64位系统的结果不同?
有人可以解释一下吗?
答案 0 :(得分:4)
1。我无法理解一些事情。首先,如何添加有符号和无符号数字将输入结果转换为无符号类型?
这是由整数提升和整数转换等级定义的。
6.3.1.8 p1:否则,如果具有无符号整数类型的操作数的等级大于或等于 等于另一个操作数的类型的等级,然后是操作数 有符号整数类型转换为带有unsigned的操作数的类型 整数类型。
在这种情况下,unsigned的排名高于int,因此int被提升为unsigned。
将int(-2)转换为unsigned,如下所述:
6.3.1.3 p2:否则,如果新类型是无符号的,则通过重复添加或转换该值 减去一个可以在新类型中表示的最大值 直到该值在新类型的范围内
2。如果结果确实是无符号类型的0xFFFFFFFF,为什么在32位系统中,在用ptr添加它时,它是否会被解释为ptr-1,假设该数字实际上是无符号类型而前导1不应该表示符号? / em>的
这是未定义的行为,不应该依赖,因为C没有定义指针算术溢出。
6.5.6 p8:如果两个指针都有 操作数和结果指向同一个数组对象的元素,或者指向最后一个数组对象的元素 数组对象的元素,评估不得产生溢出;否则, 行为未定义。
3。第二,为什么64位系统的结果不同?
(假设(如图所示)int和unsigned是4个字节。)
A和B的结果与 1。中描述的相同,然后将该结果添加到指针。由于指针是8个字节并且假设添加没有溢出(如果ptr具有大地址,仍然可以提供与 2中相同的未定义行为。),结果是一个地址。
这是未定义的行为,因为指针指向数组范围之外的方式。
答案 1 :(得分:2)
表达式A + B
的操作数受常规算术转换的影响,包含在C11(n1570)6.3.1.8 p1中:
[...]
否则,将对两个操作数执行整数提升[使
int
和unsigned int
保持不变]。然后将以下规则应用于提升的操作数:
- 如果两个操作数具有相同的类型,[...]
- 否则,如果两个操作数都有有符号整数类型或两者都有无符号整数类型,[...]
- 否则,如果具有无符号整数类型的操作数的秩大于或等于另一个操作数的类型的秩,则带有符号整数类型的操作数将转换为具有无符号整数类型的操作数的类型。 / LI>
- [...]
类型int
和unsigned int
具有相同的等级(同上.6.3.1.1 p1,4 th 项目符号);添加的结果具有类型unsigned int
。
在32位系统上,int
和指针通常具有相同的大小(32位)。从以硬件为中心的角度来看(假设有2个补码),减去1
和添加-1u
是相同的(对于有符号和无符号类型的添加是相同的!),所以对数组元素的访问似乎有效。
但是,这是未定义的行为,因为array
不包含0x100000003 rd 元素。
在64位上,int
通常仍有32位,但指针有64位。因此,没有环绕性和减去1的等价性(从以硬件为中心的观点来看,两种情况下的行为都是不确定的。)
为了说明,说ptr
是0xabcd0123,添加0xffffffff产量
abcd0123
+ ffffffff
1abcd0122
^-- The 1 is truncated for a 32-bit calculation, but not for 64-bit.
答案 2 :(得分:1)
在大多数64位上,int
是32位,但在32位系统上,指针也是32位。
回想一下,在基于two's complement的硬件的32位算术中,添加0xFFFFFFFF
几乎与减1相同:它溢出并且你得到相同的数字减1(它是相同的当你将0添加到0到9之间的数字时,你得到的数字是数字减去1和一个进位)。在这种类型的硬件上,-1
的编码实际上是相同的值0xFFFFFFFF
,只有操作是不同的(带符号的添加与无符号的添加),因此将在无符号的情况下生成进位。
在64位指针上是...... 64位。将32位值添加到64位需要将该32位值扩展为64. unsigned
值为零扩展(即,缺失位仅用零填充),而有符号值是符号扩展(即缺少位)填充符号位值)。
在这种情况下,添加无符号值(因此不会进行符号扩展)不会溢出,从而产生与原始值非常不同的值。