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

9

我看到有几篇帖子(例如size_t vs. uintptr_t)涉及 size_t 和 uintptr_t/ptrdiff_t,但没有关于这些新的 C99 指针大小类型相对大小的讨论。

示例机器:普通的 Ubuntu 14LTS x64,gcc 4.8:

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

输出结果为:"8, 8, 8"

对我来说这没有意义,因为我期望diff类型(差异类型)必须是有符号的,需要比无符号指针本身使用更多的位。

考虑:

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

这段文本涉及到IT技术,其中“2的补码”表示负数,无法适配64位;因此,我认为ptrdiff_t应该比ptr_t更大。

[一个相关问题是为什么intptr_t和uintptr_t大小相同....虽然我认为这可能只是允许有符号类型包含表示的位(例如,在负指针上使用有符号算术(a)是未定义的,并且(b)具有受限制的效用,因为指针根据定义是“正数”)]

谢谢!


1
intptr_t和uintptr_t之间的唯一保证是它不会更小。实际上,所有真正发生的事情就是你在说(使用uintptr_t)“不会有带符号的表示”,无论在实现中是什么(很可能是2的补码,因为有很多非常好的理由)。 - David Hoelzer
4
在整数表达式中不要使用 NULL!如果你的意思是整数 0,请写成 0NULL 可以表示为 (void *)0,这会导致表达式结果未定义(对空指针进行算术运算是未定义的)。 - too honest for this site
将两个整数或无符号整数相加/相减的原因相同,即使您需要1个以上的位来避免溢出正确表示结果,也会产生相同的类型。仅仅是拥有比无符号版本长1位的类型是不切实际的,如果我们可以拥有那种类型,为什么不在需要时将新的有符号类型用作无符号类型呢?然后我们将需要一个有符号类型再多1位,递归问题无法解决。 - phuclv
2个回答

24
首先,很明显不清楚为什么要在这里使用uintptr_t。这些语言(C和C ++)不允许您从任意指针值中减去任何指针值。只有当两个指针指向同一对象(同一个数组对象)时才可以相减。否则,行为将是未定义的。这意味着这两个指针之间的距离不可能超过SIZE_MAX字节。请注意:距离受size_t的范围限制,而不是uintptr_t的范围限制。在一般情况下,uintptr_t可以比size_t更大。没有人在C / C ++中保证您能够将位于UINTPTR_MAX字节处的两个指针相减。
(是的,我知道在平面内存平台上uintptr_tsize_t通常是相同的类型,至少在范围和表示方面是如此。但是从语言的角度来看,假设它们总是相同的是不正确的。)
其次,在从不相关的uintptr_t切换到更相关的size_t之后,可以说您的逻辑是完全有效的。sizeof(ptrdiff_t)应该大于sizeof(size_t),因为需要一个额外的位来表示有符号结果。尽管如此,无论听起来多么奇怪,语言规范也不要求ptrdiff_t足够宽以容纳所有指针减法结果,即使两个指针指向同一对象的不同部分(即它们之间的距离不超过SIZE_MAX字节)。ptrdiff_t被合法地允许具有与size_t相同的位计数。
这意味着,“看似有效”的指针减法实际上可能会导致未定义的行为,只是因为结果太大了。如果您的实现允许您声明大小为SIZE_MAX / 3 * 2char数组
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 范围内的结果。

另请参见:


5
@Olaf: 不,这并不是错误的。在平面内存平台上,uintptr_tsize_t的大小相同,这只是平面内存平台的一个偶然而完全无关紧要的属性。在分段内存平台上,size_t通常比uintptr_t小(分段内存平台通常支持不同的内存模型,这会决定size_tuintptr_t的相对大小)。并且从抽象语言的角度来看,在一般情况下 sizeof(size_t) <= sizeof(uintptr_t) - AnT stands with Russia
5
@Olaf:DOS、Win16、分段式IBM大型机……但现在这些已经不重要了。我完全不关心它们。只要语言规范保持这种区别,它就会存在。这就是我们在语言中拥有size_tuintptr_t两个独立概念的全部原因。如果没有这种区别,根本不需要使用uintptr_t——size_t完全可以胜任(而且许多人已知滥用它来达到这个目的)。 - AnT stands with Russia
1
一个高质量的实现不会允许创建大于PTRDIFF_MAX的对象。 - R.. GitHub STOP HELPING ICE
1
@AnT 我的应用程序中使用的部分是一段复制代码的代码块:字面上是 &labelA - &LabelB,以计算对象代码段的实际布局大小...显然非常依赖于平台。我在这里提出问题的原因是想要从两个指针相减得到一个有符号类型的结果-具体来说,我可以只做一个 abs() 而不必担心哪个更大。我听到的是没有这样的标准有符号类型? - some bits flipped
1
@Evan Carroll:我建议你有时间的时候,可以看一下我在这个答案中概述的“处理size_t/ptrdiff_t困境的三种选择”。链接如下:https://dev59.com/EFgQ5IYBdhLWcg3wkEtJ#42594384 - AnT stands with Russia
显示剩余19条评论

-2

接受的答案并没有错,但是并没有提供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

1
与指针大小相同。你多次重复这个短语。你假设指针只有一个固定的大小,但实际上并非如此。 - Andrew Henle
1
尽管0 - &a是未定义行为,但无论它是什么都必须适合于ptrdiff_t。您的意思是UB意味着有一些结果,只是您不知道它是什么。事实并非如此,这将是“未定义结果”。 UB比那更糟糕:程序可能会崩溃,或者其他不相关的代码部分可能会崩溃。(但是在这种情况下崩溃只有在使用-fsanitize=undefined时才有可能发生)。请参见http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html和[Does the C++ standard allow for an uninitialized bool to crash a program?](//stackoverflow.com/a/54125820)。 - Peter Cordes
1
ISO C++并不保证如果A-B产生未定义行为,则(A-B)+B==A。它可能会崩溃或发生其他任何事情。当然,它也可以正常工作,在几乎任何没有故意敌对的实现(以及没有开启UB检测的实现)中都是如此,特别是具有平面内存模型的实现。但是,重要的是要区分在实践中真实世界的行为与ISO C++对语言设计的确切保证,这对于关于语言设计的SO答案非常重要。 - Peter Cordes
1
@EvanDark:[u]intptr_t甚至不需要存在 - Andrew Henle
1
调用malloc是未定义行为,因为将(void *)强制转换为与之前不同的任何其他类型都是未定义行为。有时我希望我们可以对评论进行反对投票。该评论表明存在相当大的误解。 - Andrew Henle
显示剩余7条评论

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