【问题标题】:why is sizeof(ptrdiff_t) == sizeof(uintptr_t)为什么 sizeof(ptrdiff_t) == sizeof(uintptr_t)
【发布时间】:2015-08-21 01:08:46
【问题描述】:

我看到几篇关于 size_t 与 uintptr_t/ptrdiff_t 的帖子(例如 size_t vs. uintptr_t),但没有关于这些新的 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 类型需要比无符号 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 被定义为“正”)]

谢谢!

【问题讨论】:

  • 您在 intptr_t 和 uintptr_t 之间的唯一保证是它不会更小。事实上,真正发生的一切是你在说(使用 uintptr_t)“将没有签名表示”,无论实现中发生了什么(很可能是 2 的补码,因为很多非常好的理由)跨度>
  • 不要在整数表达式中使用NULL!如果你是整数0,写出来吧! NULLcan be (void *)0`,使表达式结果未定义(空指针上的算术未定义。
  • 加/减 2 个整数或无符号整数的相同原因导致相同类型,即使您需要多 1 位以避免溢出正确表示结果。拥有比无符号版本长 1 位的类型是不切实际的,如果我们可以拥有这种类型,为什么不在需要时将新的有符号类型用作无符号类型呢?然后我们需要一个更长的有符号类型 1 并且递归问题无法解决

标签: c++ c pointers c99 computer-architecture


【解决方案1】:

首先,很明显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_tsize_t 具有相同的位数。

这意味着“看似有效”的指针减法实际上可能仅仅因为结果太大而导致未定义的行为。如果您的实现允许您声明一个大小为 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范围的结果。

另见,

【讨论】:

  • @Olaf:不,没有错。 unintptr_t 与平面内存平台上的size_t 大小相同,这只是平面内存平台的巧合且完全无关紧要的属性。在分段内存平台上,size_t 通常小于uintptr_t(分段内存平台通常支持不同的内存模型,这将决定size_tuintptr_t 的相对大小)。在一般情况下,从抽象语言的角度来看sizeof(size_t) <= sizeof(uintptr_t)
  • @Olaf:DOS、Win16、分段 IBM 大型机……但现在变成了什么完全无关紧要。我根本不在乎。只要语言规范保持这种差异化,它就会存在。这就是我们在语言中拥有独立的size_tuintptr_t 概念的全部原因。如果它不存在,则根本不需要 uintptr_t - size_t 可以很好地达到这个目的(众所周知,很多人为此目的滥用它)。
  • 高质量的实现不允许创建大于PTRDIFF_MAX的对象。
  • @AnT 我的应用程序中用到的部分是复制代码的代码块:字面意思是&labelA - &LabelB,用于计算目标代码段的实际布局大小......显然非常依赖于平台。在这里引发我的问题是想要一个有符号类型作为减去两个指针的结果——特别是这样我就可以做一个abs() 而不必担心哪个更大。我听说没有这样的标准签名类型?
  • @Evan Carroll :我建议也许,当你有时间的时候,你可以看看我在这个答案中概述的“处理size_t/ptrdiff_t dilemma 的三种选择”: stackoverflow.com/a/42594384/187690
【解决方案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

【讨论】:

  • 和指针大小一样 你重复这句话好几次。你假设指针有一个单一的大小。他们不必这样做。
  • 虽然 0 - &a 确实是未定义的行为 [...],但无论它是什么,都必须适合 ptrdiff_t - 你是在暗示 UB 意味着有一些结果,你只是不知道它是什么。事实并非如此,那将是一个“未定义的结果”。 UB 比这更糟糕:程序可能会崩溃,或者代码的其他不相关部分可能会中断。 (但只有-fsanitize=undefined 才有可能崩溃)。见blog.llvm.org/2011/05/what-every-c-programmer-should-know.htmlDoes the C++ standard allow for an uninitialized bool to crash a program?
  • ISO C++ 确实保证(A - B) + B == A 如果A-B 产生未定义的行为。它允许崩溃或其他任何事情。当然,它也被允许只是工作,这几乎是任何不是故意敌对的实际实现(并且没有打开 UB 检测)都会发生的事情,尤其是那些具有平坦内存模型的实现。但重要的是要区分实际的实际行为与 ISO C++ 真正保证的行为,以获得关于语言设计的 SO 答案。
  • 如果您编写的 90% 的 C++ 具有未定义的行为,您可能需要考虑更仔细地执行此操作。大多数代码有很多东西可能在一些假设的实现中是 UB,例如关于类型和其他实现定义的东西的假设,但在大多数 C++ 中只有 10% 实际上是良性的 UB 在开发人员真正关心的主流实现上运行,希望远低于 1%,但我对此没有很好的估计.
猜你喜欢
  • 2013-09-24
  • 1970-01-01
  • 2012-06-14
  • 2019-08-17
  • 2012-08-27
  • 2011-03-07
  • 1970-01-01
  • 1970-01-01
  • 2021-05-12
相关资源
最近更新 更多