为什么 sizeof(ptrdiff_t) == sizeof(uintptr_t)

why is sizeof(ptrdiff_t) == sizeof(uintptr_t)

我看到一些关于 size_t 与 uintptr_t/ptrdiff_t 的帖子(例如 size_t vs. uintptr_t),但是 none 关于这些新的 c99 ptr 大小类型的相对大小。

示例机器:vanilla ubuntu 14lts x64,gcc 4.8:

printf("%zu, %zu, %zu\n", sizeof(uintptr_t), sizeof(intptr_t), sizeof(ptrdiff_t));

打印:“8, 8, 8”

这对我来说没有意义,因为我希望必须签名的 diff 类型比 unsigned ptr 本身需要更多的位。

考虑:

NULL - (2^64-1)  /*largest ptr, 64bits of 1's.*/

2 的负补码不适合 64 位;因此我希望 ptrdiff_t 大于 ptr_t。

[一个相关的问题是为什么 intptr_t 与 uintptr_t 的大小相同....虽然我很舒服这可能只是为了允许一个带符号的类型包含表示的位(例如,在负 ptr 上使用带符号的算术将 (a) 未定义,并且 (b) 实用性有限,因为 ptrs 根据定义 "positive")]

谢谢!

首先,很明显 uintptr_t 不是在这里做什么。语言(C 和 C++)不允许您从彼此中减去任何任意指针值。两个指针只有在指向同一个对象(指向同一个 array 对象)时才能相减。否则,行为未定义。这意味着这两个指针之间的距离不可能超过 SIZE_MAX 字节。注意:距离受size_t范围限制,不受uintptr_t范围限制。在一般情况下 uintptr_t 可以是比 size_t 更大的类型。 C/C++ 中没有人向您保证您应该能够减去两个相距 UINTPTR_MAX 字节的指针。

(是的,我知道在平面内存平台上 uintptr_tsize_t 通常是相同的类型,至少在范围和表示方面是这样。但从语言的角度来看,它是错误地认为它们总是如此。)

您的NULL - (2^64-1)(如果解释为地址减法)就是这种有问题的减法的明显例子。是什么让您认为您应该首先能够做到这一点?

其次,从不相关的 uintptr_t 切换到更相关的 size_t 之后,可以说您的逻辑是完全正确的。 sizeof(ptrdiff_t) 应该大于 sizeof(size_t) 因为需要一个额外的位来表示带符号的结果。尽管如此,无论听起来多么奇怪,语言规范并不要求 ptrdiff_t 足够宽以容纳所有指针减法结果,即使两个指针指向同一对象的部分(即它们不超过 SIZE_MAX 字节)。 ptrdiff_t 在法律上允许与 size_t.

具有相同的位数

这意味着 "seemingly valid" 指针减法实际上可能仅仅因为结果太大而导致未定义的行为。如果您的实现允许您声明一个大小为 char 的数组,例如 SIZE_MAX / 3 * 2

char array[SIZE_MAX / 3 * 2]; // This is smaller than `SIZE_MAX`

如果 ptrdiff_tsize_t

的大小相同,则减去指向此数组结尾和开头的完全有效的指针可能会导致未定义的行为
char *b = array;
char *e = array + sizeof array;

ptrdiff_t distance = e - b; // Undefined behavior!

这些语言的作者决定选择这种更简单的解决方案,而不是要求编译器实现对[可能非本地]超宽有符号整数类型的支持ptrdiff_t

现实生活中的实现意识到了这个潜在的问题,通常会采取措施避免它。他们人为地限制了最大支持对象的大小,以确保指针减法永远不会溢出。在典型的实现中,您将无法声明大于 PTRDIFF_MAX 字节(大约 SIZE_MAX / 2)的数组。例如。即使 SIZE_MAX 在您的平台上是 264-1,该实现也不会让您声明任何大于 263- 的值1 个字节(并且来自其他因素的现实生活限制可能比这更严格)。有了这个限制,任何合法的指针减法都会产生一个符合 ptrdiff_t.

范围的结果

另见,

接受的答案并没有错,但并没有深入了解为什么 intptr_t、size_t 和 ptrdiff_t 实际上有用,以及如何使用它们。所以这里是:

  • size_t 基本上是 size_of 表达式的类型。它只需要能够容纳您可以制作的最大对象的大小,包括数组。因此,如果您只能使用 64k 连续内存,那么 size_t 可以低至 16 位,即使您有 64 位指针。

  • ptrdiff_t是指针差异的类型,例如&a - &b。虽然 0 - &a 确实是未定义的行为(就像 几乎 C/C++ 中的所有内容一样),但无论它是什么,都必须符合 ptrdiff_t.它通常与指针大小相同,因为这样最有意义。如果 ptrdiff_t 是一个奇怪的大小,指针算法本身就会崩溃。

  • intptr_t/uintptr_t 与指针大小相同。它们符合相同的 int*_t 模式,其中 * 是 int 的大小。与所有 int*_t/uint*_t 类型一样,出于某种原因,标准允许它们 更大 然后是必需的,但这非常罕见。

根据经验,您可以对大小和数组索引使用 size_t,对与指针相关的所有内容使用 intptr_t/uintptr_t。不要使用 ptrdiff_t.