size_t与uintptr_t


246

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

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

因此,在C99中,该标准引入了intptr_tuintptr_t类型,保证可以容纳指针的有符号和无符号类型:

uintptr_t p = (size_t) v;

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

出于同样的原因,ptrdiff_t它是一个有符号的类型,能够保存指针差异,因此能够保存大多数指针,那么它与intptr_t有何区别?

所有这些类型难道不是都为同一个功能提供了完全不同的版本吗?如果没有,为什么?我不能对其中一个进行处理,而对另一个却不能进行处理?如果是这样,为什么C99在该语言中添加了两种本质上多余的类型?

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

Answers:


236

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

不必要!追溯到分段16位体系结构的时代,例如:阵列可能仅限于一个段(因此16位size_t就可以),但是您可能有多个段(因此需要32位intptr_t类型来选择)段以及其中的偏移量)。我知道在统一寻址的非分段体系结构的时代,这些事情听起来很奇怪,但是,标准必须满足比“ 2009年的正常情况”更广泛的变化!-)


6
这以及其他许多人都得出了相同的结论,解释了size_t和之间的区别,uintptr_t但是ptrdiff_t又和intptr_t-两者都无法在几乎任何平台上存储相同范围的值吗?为什么要同时使用有符号和无符号指针大小的整数类型,特别是如果ptrdiff_t已经达到有符号指针大小的整数类型的目的。
克里斯·卢茨

8
关键字“ @ 几乎在任何平台上都有”,@ Chris。一个实现可以自由地将指针限制在0xf000-0xffff范围内-这需要一个16位的intptr_t但只需要一个12/13位的ptrdiff_t。
paxdiablo

29
@Chris,仅对于同一数组内的指针定义它们的区别是明确的。因此,在完全相同的分段16位体系结构上(数组必须位于单个段中,但是两个不同的数组可以位于不同的段中),指针必须为4个字节,但指针差异可以为2个字节!
亚历克斯·马丁里

6
@AlexMartelli:除了指针差异可以是正数或负数。该标准要求size_t至少为16位,但ptrdiff_t至少应为17位(实际上这意味着至少32位)。
基思·汤普森

3
没关系分段式架构,像x86-64这样的现代架构又如何呢?此体系结构的早期实现只为您提供48位可寻址空间,但指针本身是64位数据类型。您可以合理寻址的最大连续内存块将是48位,因此我必须想象SIZE_MAX不应该是2 ** 64。请注意,这是使用平面寻址。不需要分段即可使SIZE_MAX数据指针的范围与数据指针的范围不匹配。
安东·科尔曼

89

关于您的声明:

“ C标准保证该size_t类型可以容纳任何数组索引。从逻辑上讲,这意味着size_t应该可以容纳任何指针类型。”

这实际上是一种谬论(由于错误的推理导致的误解)(a)。您可能会认为后者是前者的后继者,但实际上并非如此。

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

C99声明size_t变量的上限由定义,SIZE_MAX并且可以低至65535(请参见C99 TR3,7.18.3,在C11中未更改)。如果指针在现代系统中仅限于此范围,则会受到相当大的限制。

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


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

所有的小狗都很可爱。这个东西很可爱。因此,这东西一定是小狗。

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

这类似于您的第一条陈述,不一定要强制第二条陈述。


我不会重复输入我在Alex Martelli的评论中所说的内容,而只是感谢您的澄清,而是重申我问题的下半部分(ptrdiff_tvs. intptr_t部分)。
克里斯·卢茨

5
与大多数交流一样,@ Ivan需要对某些基本项目达成共识。如果您认为此答案“很有趣”,那么我向您保证,这是我的意图的误解。假设您指的是我的“逻辑谬误”评论(我看不到任何其他可能性),那是作为事实陈述,而不是某些要以OP为代价的陈述。如果您想提出一些具体的改进措施以最大程度地减少误解的可能性(而不仅仅是一般性的投诉),我很乐意考虑。
paxdiablo

1
@ivan_pozdeev-这是对令人讨厌而激烈的编辑,我看不出有证据表明paxdiablo在嘲笑任何人。如果我是OP,我会推出这种右后卫....
无中生有

1
@Ivan对您建议的修改并不十分满意,已回滚并尝试删除任何意外的犯罪行为。如果您要进行其他更改,建议您开始聊天,以便我们进行讨论。
paxdiablo '18 -10-9

1
@paxdiablo好的,我想“这实际上是一个谬论”不太光顾。
ivan_pozdeev '18 -10-10

36

我将让所有其他答案在有关段限制,异乎寻常的体系结构等的推理中站出来。

名称名称上的简单差异是否足以为适当的事物使用适当的类型?

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

否则,您可以使用所有功能unsigned long(或在现代unsigned long long)。大小不是全部,类型名称带有含义,这很有用,因为它有助于描述程序。


我同意,但是我正在考虑某种骇人听闻的技巧(当然,我会清楚地记录在案),涉及在size_t字段中存储指针类型。
克里斯·卢茨

@MarkAdler Standard不需要将指针完全表示为整数:任何指针类型都可以转换为整数类型。除非先前指定,否则结果是实现定义的。如果结果不能用整数类型表示,则行为是不确定的。结果不必在任何整数类型的值范围内。因此,只有void*intptr_tuintptr_t保证是能够表示任何指针数据。
安德鲁Svietlichnyy

12

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

尽管这些不再在台式机中普遍使用,但是C标准旨在支持甚至小型的专业架构。例如,仍在开发使用8或16位CPU的嵌入式系统。


但是您可以像数组一样索引指针,所以size_t也应该能够处理它?还是将某个遥远的细分市场中的动态数组限制为仅在其细分市场中建立索引?
克里斯·卢茨

仅在技术上支持索引指针指向它们所指向的数组的大小-因此,如果将数组限制为64KB大小,那就是指针算法需要支持的全部。但是,MS-DOS编译器确实支持“巨大”内存模型,该模型中操纵了远的指针(32位分段指针),因此它们可以将整个内存作为一个单独的数组进行寻址-但是对幕后指针所做的算术是非常丑陋-当偏移量增加到超过16(或其他值)时,偏移量会回绕为0,而段部分也会增加。
迈克尔·伯

7
阅读en.wikipedia.org/wiki/C_memory_model#Memory_segmentation 并为死去的MS-DOS程序员哭泣,以使我们有空。
Justicle

更糟糕的是,stdlib函数没有处理HUGE关键字。16位MS-C的所有str功能和Borland即使对于mem函数(memsetmemcpymemmove)。这意味着当偏移量溢出时,您可以覆盖部分内存,这在我们的嵌入式平台上进行调试很有趣。
帕特里克·施吕特(PatrickSchlüter)

@Justicle:在C语言中没有很好地支持8086分段体系结构,但是我知道没有其他体系结构在1MB地址空间足够而64K地址空间不够的情况下效率更高。某些现代JVM实际上非常类似于x86实模式使用寻址,即将32位对象引用左移3位以在32GB地址空间中生成对象基地址。
2015年

5

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

例如,即使Windows上的unsigned shortwchar_t大小相同(我认为),使用wchar_t代替unsigned short表示您将使用它存储宽字符而不是仅仅存储任意数字的意图。


但是,这里有一个区别-我的系统上,wchar_t是比大得多unsigned short所以使用一个其他将是错误的,并造成严重的(现代)便携性的关注,而之间的可移植性的关注size_tuintptr_t似乎横亘在遥远的土地1980年左右的东西(约会中在黑暗中暗中刺伤)
克里斯·卢茨

碰!但话又说回来,size_t并且uintptr_t仍然隐含使用它们的名称。
dreamlax

他们这样做了,我想知道是否有除简单性之外的动机。事实证明那里。
克里斯·卢茨

3

向前和向后看,回想起各种怪异的体系结构散布在整个景观中,我很确定它们正在尝试包装所有现有系统,并提供所有可能的未来系统。

可以肯定的是,到目前为止,我们并不需要太多类型的东西。

但是,即使在LP64(一种相当常见的范例)中,我们也需要size_t和ssize_t作为系统调用接口。可以想象一个更受限制的旧系统或将来的系统,在该系统中使用完整的64位类型比较昂贵,他们可能想在大于4GB的I / O操作上穿梭,但仍然具有64位指针。

我认为您必须怀疑:可能会开发出什么,将来会发生什么。(也许是128位分布式系统的Internet范围内的指针,但是在系统调用中不能超过64位,或者甚至可能是“旧版” 32位限制。:-)老式系统可能会使用新的C编译器的图像。 。

另外,看看周围存在什么。除了数量庞大的286实模式存储模型外,CDC 60位字/ 18位指针大型机怎么样?Cray系列怎么样?没关系,普通的ILP64,LP64,LLP64。(我一直以为Microsoft广泛使用LLP64,应该是P64。)我当然可以想象有一个委员会试图覆盖所有基础...


-9
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,反之亦然。


10
所有这些说明都是C的一个特殊语法。数组索引的定义是x [y]等于*(x + y),并且由于a + 3和3 + a的类型和值相同,因此可以使用3 [a]或a [3]。
弗雷德·纽克
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.