使用int和size_t时结果的区别

6
我正在阅读一篇关于使用size_tptrdiff_t数据类型的文章,可以在这里找到。当我看到这个例子时,需要注意以下代码:

enter image description 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类型?如果结果确实是unsigned类型的0xFFFFFFFF,那么在32位系统中,当与ptr相加时,为什么会被解释为ptr-1,考虑到这个数字实际上是unsigned类型,而前导位1不应该表示符号?

其次,在64位系统中为什么结果不同?

请问有人能解释一下吗?


3
邮政编码,不是代码的图片。 - chux - Reinstate Monica
请参见https://dev59.com/dIHba4cB1Zd3GeqPU7dG。 - technosaurus
很高兴有人对我的文章感兴趣。这些问题已经得到了解答。关于地址算术的更多细节请参考:http://www.viva64.com/en/l/0013/ 此外,还有许多关于64位错误的有趣内容,请参考:http://www.viva64.com/en/l/full/ - user965097
我仍在寻找更好的答案。如果没有更好的答案,我将在接下来的1-2天内给予声望。相信我,我知道它是如何工作的。 - SexyBeast
3个回答

4

1. 我不理解几件事情。首先,为什么将一个有符号数和一个无符号数相加后,结果被转换成无符号类型?

这由整型提升和整型转换等级定义。

6.3.1.8第1段:否则,如果具有无符号整数类型的操作数的等级大于或等于其他操作数类型的等级,则带符号整数类型的操作数将转换为具有无符号整数类型的操作数的类型。

在这种情况下,无符号数的等级高于int,因此int被提升为无符号数。

将int(-2)转换为无符号数的过程如下:

6.3.1.3第2段:否则,如果新类型是无符号的,则通过反复添加或减去可以在新类型中表示的最大值加1,直到该值在新类型的范围内为止来转换该值

2. 如果结果确实是无符号类型的0xFFFFFFFF,在32位系统中与ptr相加时,为什么会被解释为ptr-1,考虑到数字实际上是无符号类型,而开头的1不应该表示符号?

这是未定义行为,不应该依赖它,因为C不定义指针算术溢出。

6.5.6第8段:如果指针操作数和结果都指向同一数组对象的元素或数组对象的最后一个元素之一,则评估不会产生溢出;否则,行为未定义。

3. 其次,在64位系统中为什么结果不同?

(这假设(如图片所示),int和unsigned均为4个字节。)

A和B的结果与1.中描述的相同,然后将该结果添加到指针中。由于指针为8字节,并且假设加法不会溢出(如果ptr具有大地址,则仍可以发生溢出,产生与2.中相同的未定义行为),因此结果是一个地址。

这是未定义行为,因为指针指向数组范围之外的内容。


2
表达式 A + B 的操作数受到通常的算术转换的影响,该算术转换在 C11 (n1570) 6.3.1.8 p1 中有所涉及:

[...]

否则,在两个操作数上执行整数提升 [保留 intunsigned int 不变]。然后将以下规则应用于提升后的操作数:

  • 如果两个操作数具有相同的类型,则[...]
  • 否则,如果两个操作数都具有带符号整数类型或都具有无符号整数类型,则[...]
  • 否则,如果具有无符号整数类型的操作数的等级大于或等于另一个操作数的类型的等级,则将带符号整数类型的操作数转换为具有无符号整数类型的操作数的类型。
  • [...]
类型 intunsigned int 具有相同的等级(ibid. 6.3.1.1 p1, 第4个项目);加法的结果类型为 unsigned int
在32位系统中,int 和指针通常具有相同的大小(32位)。从硬件中心的角度来看(假设为2的补码),减去1并添加 -1u 是相同的(有符号和无符号类型的加法是相同的!),因此似乎可以访问数组元素。
然而,这是未定义行为,因为 array 不包含第0x100000003个元素。
在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.

1
在大多数64位系统上,int为32位,但在32位系统上,指针也为32位。

请注意,在32位算术 - 基于二进制补码的硬件上,添加0xFFFFFFFF几乎相当于减去1:它会溢出并使得该数字减去1(当您将9加到0和9之间的数字时,您可以得到该数字减1和一个进位)。在这类硬件上,-1的编码实际上是同样的值0xFFFFFFFF,只有操作不同(有符号加和无符号加),因此在无符号情况下会产生进位。

在64位指针中... 64位。将32位值添加到64位值需要将该32位值扩展为64位。无符号值是零扩展的(即缺失的位只填充为零),而有符号值是符号扩展的(即缺失的位填充为符号位值)。
在这种情况下,添加一个无符号值(因此不会进行符号扩展)不会溢出,从而产生与原始值非常不同的值。

我从来不擅长这些位操作。:( 所以,明确一下,在加法中,结果是原始数字-1,进位被忽略,因此数字变为原始数字-1,对吗?为什么只在32位系统上发生这种情况? - SexyBeast
1
u64 + u32 仍然可能溢出。 - 2501

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