Size_t vs. uinttr_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要在语言中添加两个本质上多余的类型?

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

63514 次浏览

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

虽然这些在桌面计算机中已经不常用了,但C标准旨在支持甚至是小型的、专门的体系结构。例如,仍有嵌入式系统正在开发8位或16位cpu。

size_t是一个可以保存任何数组下标的类型。这意味着, 逻辑上,size_t应该能够 保持任何指针类型

不一定!例如,回到分段16位架构的时代:一个数组可能被限制在一个段中(因此16位的size_t就可以了),但你可以有多个段(因此需要32位的intptr_t类型来选择段以及其中的偏移量)。我知道这些事情在这些统一寻址的无分段架构的日子里听起来很奇怪,但是标准必须满足更广泛的多样性,而不是“2009年的正常情况”,你知道!-)

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

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

关于你的声明:

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

这实际上是一个谬论(由不正确的推理引起的误解)(一)。你可以认为,后者是从前者,但实际情况并非如此。

指针和数组下标是一样的东西。设想一个符合规范的实现,将数组限制在65536个元素,但允许指针将任何值寻址到一个巨大的128位地址空间,这是相当合理的。

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

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


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

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

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

这类似于你的第一个陈述,但不一定要求第二个。

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

当然,事情解决的方式,到目前为止,我们不需要那么多类型。

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

我想你们会想:我们已经开发了什么,未来又会有什么。(也许是128位分布式系统互联网范围的指针,但在一次系统调用中不超过64位,或者甚至可能是“遗留的”32位限制。:-)想象一下遗留系统可能会得到新的C编译器…

另外,看看当时的情况。除了数不胜数的286实模式内存模型,CDC的60位字/ 18位指针大型机怎么样?克雷系列怎么样?不管正常的ILP64 LP64 LLP64。(我一直认为微软在LLP64上矫揉造作,它应该是P64。)我当然可以想象一个委员会试图涵盖所有基础……

关于段限制、奇异架构等推理,我将让所有其他答案代表它们自己。

难道简单的名称差异还不足以让我们为正确的事情使用正确的类型吗?

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

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

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

size_t vs. uintptr_t

除了其他好的答案:

size_t<stddef.h>, <stdio.h>, <stdlib.h>, <string.h>, <time.h>, <uchar.h>, <wchar.h>中定义。至少16位。

uintptr_t<stdint.h>中定义。它是可选。兼容的库可能不会定义它,可能是因为没有足够宽的整数类型来往返void*-uintptr_t-void *

两者都是无符号整数类型。

注意:可选的同伴intptr_t是一个带符号整数类型。