为什么~size_t(0)(在大多数32位系统中= = 0xFFFFFFFF)不是有效的数组索引?

时间:2011-09-04 22:47:55

标签: c++ c memory

引用此博文:

http://www.codesynthesis.com/~boris/blog/2008/10/13/writing-64-bit-safe-code/

  

这是有效的,因为有效的内存索引只能在[0,~size_t(0)-1]范围内。例如,在std :: string中使用相同的方法。

那么为什么~size_t(0)(这通常应该在32位系统中等于0xFFFFFFFF)不是有效的数组索引?我假设如果你有32位你应该能够引用整个范围[0,0xFFFFFFFF],不是吗?

5 个答案:

答案 0 :(得分:47)

  

重要提示:术语“内存索引”含糊不清且令人困惑。链接文章严格指代数组索引,而不是内存中的地址。 size_t无法表示所有内存地址是完全有效的,这就是我们在C99中使用intptr_t类型的原因。当然,这不适用于您的工作站,它无疑具有简单的Von Neumann类型架构。 (此问题已被编辑以删除对“内存索引”的引用。)

C标准保证size_t可以保存任何数组的大小。但是,对于任何数组a[N],标准保证a + N必须是有效指针,并且比较不等于任何指向a元素的指针。

因此,size_t必须能够表示至少一个大于任何可能的数组索引的值。由于~(size_t)0保证是最大size_t值,因此它是数组索引的标记的一个很好的选择。

<强>讨论:

  1. 为什么~(size_t)0保证是最大值?因为标准明确地这样说:来自§6.5.3.3:“如果提升类型是无符号类型,表达式~E等效于该类型中可表示的最大值减去E。“请注意,保证(size_t)-1也是从有符号类型到无符号类型的转换规则的最大值。很遗憾,在您的平台上找到SIZE_MAX的定义并不总是很容易,因此首选(size_t)-1~(size_t)0。 (请注意,如果int可以代表SIZE_MAX,则不再适用......但这不会在实际系统中发生。)

  2. 从0到0的索引数组大小是多少?根据C标准,这个数组不能通过本文顶部列出的参数存在。 / p>

  3. 如果你malloc(-1),生成的内存区域必须从0开始。(FALSE)标准允许有很多非常奇怪的情况,但是在实践中遇到。例如,想象一个(uintptr_t)-1 > (size_t)-1的系统。 C标准完全按照它的方式进行措辞,因为它不仅可以在您的PC上运行,而且可以在具有哈佛架构的奇怪的小型DSP上运行,并且可以在具有拜占庭内存分段方案的古老系统上运行。还有一些具有历史意义的系统,其中NULL指针的表示形式与0不同。

答案 1 :(得分:14)

直觉就是这样:

x = malloc(~size_t(0));    // the most you can allocate
x[~size_t(0) -1];          // the highest valid index

答案 2 :(得分:13)

好吧,正如@Apprentice Queue在答案中正确指出的那样,因为C或C ++程序中最大连续对象的大小受SIZE_MAX的限制(与(size_t) -1相同,与{{1}相同他需要索引该对象的字节的最大索引是~size_t(0)。但与此同时,正如@Dietrich Epp在他的回答中正确指出的那样,C和C ++允许地址算法超出数组末尾的一个元素,这使得SIZE_MAX - 1成为有效的索引,如果不是用于访问数组元素,至少对于指针算术。所以,正式来说SIZE_MAX是一个有效的索引,即使它不能代表数组的现有元素。

然而,使用SIZE_MAX作为允许“索引整个内存”的类型的整个想法仅在某些特定平台的范围内有效,其中size_t确实恰好足够用于内存索引(“扁平内存”平台,如Win32,Win64,Linux属于此类别)。实际上,从一般的角度来看,类型size_t不足以进行内存索引。类型size_t仅保证足以在C或C ++程序中索引单个连续对象中的字节。 C和C ++都不保证支持覆盖整个地址空间的连续对象。换句话说,类型size_t保证足以索引C / C ++程序中任何显式声明的数组对象,但通常不能保证足以计算链表中的节点。然而,假设在某个特定平台上size_t的范围覆盖了整个内存,size_t的值看起来像是“保留”值的一个很好的选择,因为这个索引只能代表覆盖整个地址空间的字节数组中的最后一个字节。显然,实际索引中没有人会在实践中需要这个索引。

尽管如此,如果您真的对可以索引整个内存的正式合适类型感兴趣(并且因此能够在任何内存容器中存储元素的数量),那么(size_t) -1 ,而不是uintptr_t。该博客文章的作者似乎确实理解这里的一般问题,因为他指出size_t不适合索引文件(即用于存储文件大小或偏移)。但是,仍然很高兴注意到几乎相同的原因类型size_t也不适合索引内存。类型size_t与RAM或进程地址空间并不真正相关,这与作者在其博客文章中声称的相反。类型size_t与进程地址空间相关,但与uintptr_t无关。 size_t在他提到的平台上足够的事实只不过是那些平台的特定属性。

答案 3 :(得分:1)

是;到目前为止,我已阅读了所有答案。我现在将添加一个只有2个有效位的简单示例。最大可表示的2位无符号值为3,这意味着我们最多可以分配3个存储单元。

单位的索引是:[0,1,2] - 注意最大值3不在可寻址范围内,因为不可能只分配2位大的存储空间。

这是因为C和C ++中的索引从零开始。指数由一个&#34;关闭。因为1的大小与0的偏移重叠。这导致具有相同位数的索引比大小更多地到达一个元素。

这是答案的ELI-5版本。

实际例子:

char buffer [4]; //错误:4位无法显示2位

uint32_t buffer [1]; // HACK:我们分配了4个字符..现在是什么?

答案 4 :(得分:1)

Dietrich Epp的答案当然是完全正确的。但是,我想在其中添加一个技术原因:很难分配一个如此大的数组,~size_t(0)可以将其编入索引。

关键是,任何时候都有至少几个字节的代码映射到您的地址空间,因此您根本没有可用于映射阵列的整个虚拟地址空间。 (我甚至没有提到堆栈空间和空页的分配!)你看,可以映射一个可以用~size_t(0)索引的数组的唯一地方是地址零,它会延伸到虚拟内存的每个字节,以便最后一个字节可以被索引。这根本不可能实现。

还要考虑这一点:添加数字时,您的CPU不关心标志。对于CPU,将~size_t(0)添加到某个值与将该值递减的值相同。并且通过一个指针递减任何指针将其指向其分配的内存范围之外,除非它是null指针,它不能指向有效的内存范围...

分配不能大于~size_t(0)的语言标准限制要比这些技术限制弱得多。