ChatGPT解决这个技术问题 Extra ChatGPT

size_t 与 uintptr_t

C 标准保证 size_t 是可以保存任何数组索引的类型。这意味着,从逻辑上讲,size_t 应该能够保存任何指针类型。我在谷歌上发现的一些网站上读到这是合法的和/或应该总是有效的:

void *v = malloc(10);
size_t s = (size_t) v;

因此在 C99 中,标准引入了 intptr_tuintptr_t 类型,它们是保证能够保存指针的有符号和无符号类型:

uintptr_t p = (size_t) v;

那么使用 size_tuintptr_t 有什么区别?两者都是无符号的,并且都应该能够保存任何指针类型,因此它们在功能上看起来是相同的。除了清晰之外,是否有任何真正令人信服的理由使用 uintptr_t(或者更好的是 void *)而不是 size_t?在不透明结构中,字段仅由内部函数处理,有什么理由不这样做吗?

同样的道理,ptrdiff_t 一直是一个有符号类型,能够保存指针差异,因此能够保存大多数指针,那么它与 intptr_t 有什么区别呢?

不是所有这些类型基本上都服务于相同功能的微不足道的不同版本吗?如果不是,为什么?我不能用其中一个做什么而我不能用另一个做什么?如果是这样,为什么 C99 会在语言中添加两种本质上是多余的类型?

我愿意忽略函数指针,因为它们不适用于当前的问题,但可以随意提及它们,因为我偷偷怀疑它们将成为“正确”答案的核心。


E
Evan Carroll

size_t 是一种可以保存任何数组索引的类型。这意味着,从逻辑上讲, size_t 应该能够保存任何指针类型

不必要!回到分段 16 位架构的时代,例如:一个数组可能仅限于一个段(所以 16 位 size_t 可以)但是你可以有多个段(所以一个 32 位 intptr_t type 需要选择段以及其中的偏移量)。我知道在这些统一可寻址的未分段架构的时代,这些事情听起来很奇怪,但标准必须满足比“2009 年的正常情况”更广泛的变化,你知道的!-)


这与其他许多得出相同结论的人一起解释了 size_tuintptr_t 之间的区别,但是 ptrdiff_tintptr_t 呢?它们不能存储相同的范围几乎所有平台上的价值?为什么有有符号和无符号指针大小的整数类型,特别是如果 ptrdiff_t 已经用于有符号指针大小的整数类型的目的。
关键短语“几乎在任何平台上”,@Chris。一个实现可以自由地限制指向 0xf000-0xffff 范围的指针——这需要一个 16 位的 intptr_t 但只需要一个 12/13 位的 ptrdiff_t。
@Chris,仅对于同一数组中的指针,才能明确定义它们之间的差异。因此,在完全相同的分段 16 位架构上(数组必须位于单个段中,但两个不同的数组可以位于不同的段中)指针必须为 4 个字节,但指针差异可能为 2 个字节!
@AlexMartelli:除了指针差异可以是正的或负的。该标准要求 size_t 至少为 16 位,但 ptrdiff_t 至少为 17 位(这实际上意味着它可能至少为 32 位)。
没关系分段架构,像 x86-64 这样的现代架构呢?该架构的早期实现只为您提供 48 位可寻址空间,但指针本身是 64 位数据类型。您可以合理寻址的最大连续内存块是 48 位,所以我不得不想象 SIZE_MAX 不应该是 2**64。请注意,这是使用平面寻址;为了使 SIZE_MAX 和数据指针的范围不匹配,不需要分段。
p
paxdiablo

关于你的说法:

“C 标准保证 size_t 是一种可以保存任何数组索引的类型。这意味着,从逻辑上讲,size_t 应该能够保存任何指针类型。”

这实际上是一个谬误(由不正确的推理导致的误解)(a)。您可能认为后者是从前者继承而来的,但事实并非如此。

指针和数组索引不是一回事。设想一个将数组限制为 65536 个元素但允许指针将任何值寻址到巨大的 128 位地址空间的一致实现是相当合理的。

C99 声明 size_t 变量的上限由 SIZE_MAX 定义,可以低至 65535(参见 C99 TR3, 7.18.3,在 C11 中未更改)。如果指针在现代系统中被限制在这个范围内,那么指针将是相当有限的。

在实践中,您可能会发现您的假设成立,但这并不是因为标准保证了这一点。因为它实际上并不能保证它。

(a) 顺便说一句,这不是某种形式的人身攻击,只是说明为什么你的陈述在批判性思维的背景下是错误的。例如,下面的推理也是无效的:

所有的小狗都很可爱。这东西很可爱。所以这东西一定是小狗。

小狗的可爱与否在这里无关紧要,我只是说这两个事实并不能得出结论,因为前两句话允许存在不是小狗的可爱事物。

这类似于您的第一个声明,不一定要求第二个。


与其重新输入我在对 Alex Martelli 的评论中所说的话,我只想感谢您的澄清,但重申我问题的后半部分(ptrdiff_tintptr_t 部分)。
@Ivan,与大多数交流一样,需要对某些基本项目有共同的理解。如果您将此答案视为“开玩笑”,我向您保证这是对我意图的误解。假设您指的是我的“逻辑谬误”评论(我看不出任何其他可能性),那是事实陈述,而不是以牺牲OP为代价的陈述。如果您想提出一些具体的改进建议以最大程度地减少误解的可能性(而不仅仅是一般的抱怨),我很乐意考虑。
@ivan_pozdeev - 这是一对令人讨厌和激烈的编辑,我没有看到任何证据表明 paxdiablo 在“取笑”任何人。如果我是 OP,我会立即回滚....
@Ivan,对您提出的编辑不太满意,已回滚并试图删除任何无意的冒犯。如果您有任何其他更改要提供,我建议您开始聊天,以便我们讨论。
@paxdiablo 好吧,我想“这实际上是一个谬论”不那么傲慢了。
u
unwind

关于分段限制、奇异架构等的推理,我将让所有其他答案为自己辩护。

名称上的简单差异还不足以为正确的事物使用正确的类型吗?

如果要存储大小,请使用 size_t。如果要存储指针,请使用 intptr_t。阅读您的代码的人会立即知道“啊哈,这是某物的大小,可能以字节为单位”,以及“哦,这是一个指针值,由于某种原因被存储为整数”。

否则,您可以只使用 unsigned long(或者,在现代,unsigned long long)来处理所有事情。大小不是一切,类型名称具有有用的含义,因为它有助于描述程序。


我同意,但我正在考虑一些 hack/trick(我当然会清楚地记录),涉及将指针类型存储在 size_t 字段中。
@MarkAdler 标准不要求指针完全可以表示为整数:任何指针类型都可以转换为整数类型。除非前面指定,结果是实现定义的。如果结果不能以整数类型表示,则行为未定义。结果不必在任何整数类型的值范围内。 因此,只有 void*intptr_tuintptr_t 保证能够表示任何指向数据的指针。
这是过于幼稚的想法。例如,当您需要对齐通用结构字段时, size_t 与指针可能是错误的。然后你需要使用 uintptr_t ,因为只有这样才能保证相同的对齐和偏移。
M
Michael Burr

最大数组的大小可能小于指针。考虑分段架构 - 指针可能是 32 位,但单个段可能只能寻址 64KB(例如旧的实模式 8086 架构)。

虽然这些在桌面机器中不再常用,但 C 标准旨在支持甚至小型的专用架构。例如,仍然有使用 8 位或 16 位 CPU 开发的嵌入式系统。


但是您可以像数组一样索引指针,那么 size_t 也应该能够处理吗?或者,某个遥远段中的动态数组是否仍仅限于在其段内进行索引?
索引指针仅在技术上支持它们指向的数组的大小——因此,如果数组被限制为 64KB 大小,那么指针算术需要支持的只是这些。但是,MS-DOS 编译器确实支持“巨大”内存模型,其中远指针(32 位分段指针)被操纵,因此它们可以将整个内存作为单个数组寻址 - 但在幕后对指针进行的算术运算是非常难看 - 当偏移量增加超过 16(或其他值)时,偏移量被回绕回 0 并且段部分增加。
阅读 en.wikipedia.org/wiki/C_memory_model#Memory_segmentation 并为那些死去的 MS-DOS 程序员哭泣,这样我们才能获得自由。
更糟糕的是 stdlib 函数没有处理 HUGE 关键字。 16 位 MS-C 用于所有 str 函数,Borland 甚至用于 mem 函数(memsetmemcpymemmove)。这意味着您可以在偏移溢出时覆盖部分内存,这在我们的嵌入式平台上调试很有趣。
@Justicle:8086 分段架构在 C 中没有得到很好的支持,但我知道没有其他架构在 1MB 地址空间足够但 64K 就不行的情况下更有效。一些现代 JVM 实际上使用非常类似于 x86 实模式的寻址,使用将 32 位对象引用左移 3 位来在 32GB 地址空间中生成对象基地址。
d
dreamlax

我会想象(这适用于所有类型名称)它可以更好地传达您在代码中的意图。

例如,即使在 Windows 上 unsigned shortwchar_t 大小相同(我认为),使用 wchar_t 而不是 unsigned short 表明您将使用它来存储宽字符,而不仅仅是一些任意数。


但是这里有一个区别 - 在我的系统上,wchar_tunsigned short 大得多,因此将一个用于另一个将是错误的,并且会产生严重的(和现代的)可移植性问题,而 size_tuintptr_t 似乎位于 1980 年左右的遥远土地上(日期在黑暗中随机刺伤,那里)
触摸!但是话又说回来,size_tuintptr_t 在它们的名字中仍然有隐含的用途。
他们这样做了,我想知道除了简单的清晰度之外,是否还有其他动机。事实证明确实存在。
D
DigitalRoss

向后和向前看,并回想各种奇怪的架构散布在景观中,我很确定他们试图包装所有现有系统并提供所有可能的未来系统。

可以肯定的是,事情解决的方式,到目前为止,我们需要的类型并不多。

但即使在 LP64(一个相当常见的范例)中,我们也需要 size_t 和 ssize_t 作为系统调用接口。可以想象一个更受限制的遗留系统或未来系统,其中使用完整的 64 位类型是昂贵的,并且他们可能希望在大于 4GB 但仍具有 64 位指针的 I/O 操作上投入使用。

我想你必须想知道:可能已经开发了什么,未来可能会发生什么。 (也许是 128 位分布式系统互联网范围的指针,但在系统调用中不超过 64 位,或者甚至可能是“遗留”的 32 位限制。:-) 遗留系统可能会获得新的 C 编译器的图像.. .

另外,看看当时存在的东西。除了数以万计的 286 个实模式内存模型,CDC 60 位字/18 位指针大型机又如何呢?克雷系列怎么样?没关系正常的 ILP64、LP64、LLP64。 (我一直认为微软在 LLP64 上自命不凡,应该是 P64。)我当然可以想象一个委员会试图涵盖所有基础......


C
Chris Becke
int main(){
  int a[4]={0,1,5,3};
  int a0 = a[0];
  int a1 = *(a+1);
  int a2 = *(2+a);
  int a3 = 3[a];
  return a2;
}

暗示 intptr_t 必须始终替换 size_t ,反之亦然。


所有这些都表明了 C 的一个特殊语法怪癖。数组索引是根据 x[y] 等价于 *(x + y) 来定义的,并且因为 a + 3 和 3 + a 在类型和值上是相同的,所以你可以使用 3[a] 或 a[3]。

关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅