【发布时间】:2013-12-10 12:34:24
【问题描述】:
我有一个关于指针差异和结果类型的问题,ptrdiff_t。
C99 §6.5.6 (9) 说:
当两个指针相减时,两个指针都指向同一个数组对象的元素,或者指向数组对象最后一个元素的元素;结果是两个数组元素的下标之差。结果的大小是实现定义的,其类型(有符号整数类型)是在标头中定义的
ptrdiff_t。如果结果在该类型的对象中不可表示,则行为未定义。换句话说,如果表达式 P 和 Q 分别指向数组对象的第 i 个和第 j 个元素,则表达式 (P)-(Q) 的值是 i-j,前提是该值适合ptrdiff_t类型的对象。
§7.18.3 (2) 要求 ptrdiff_t 的范围至少为 [−65535, +65535]
如果结果太大,我感兴趣的是未定义的行为。我在标准中找不到任何保证至少与size_t 或类似的签名版本相同的范围。所以,现在我的问题是:一个符合要求的实现是否可以使 ptrdiff_t 成为有符号的 16 位类型但 size_t 是 64 位? [编辑:正如Guntram Blohm 指出的那样,16 位签名最多为 32767,所以我的示例显然不符合要求] 据我所知,我不能在严格符合代码的元素中对超过 65535 个元素的数组进行任何指针减法即使实现支持比这大得多的对象。此外,程序甚至可能崩溃。
基本原理 (V5.10) § 6.5.6 说:
在处理同一数组中的指针时,为获得正确的代数顺序,对这种类型 [
ptrdiff_t] 进行签名很重要。但是,指针差异的大小可以与可以声明的最大对象的大小一样大;由于这是无符号类型,因此两个指针之间的差异可能会导致某些实现溢出。
这可以解释为什么不需要定义指针的每个差异(指向同一数组的元素),但它没有解释为什么没有限制 PTRDIFF_MAX 至少为 SIZE_MAX/2 (带整数除法)。
为了说明,假设T 是任何对象类型,而n 是size_t 的对象,在编译时是未知的。我想为T 的n 对象分配空间,并且我想对分配范围内的地址进行指针减法。
size_t half = sizeof(T)>1 ? 1 : 2; // (*)
if( SIZE_MAX/half/sizeof(T)<n ) /* do some error handling */;
size_t size = n * sizeof(T);
T *foo = malloc(size);
if(!foo) /* ... */;
不会严格遵守,我必须这样做
if( SIZE_MAX/sizeof(T) < n || PTRDIFF_MAX < n )
相反。真的是这样吗?如果是这样,有人知道这样做的原因吗(即不需要PTRDIFF_MAX >= SIZE_MAX/2 [编辑:将> 更改为>=] 或类似的东西)?
(*) 第一个版本中的一半是我在写这篇文章时认出来的,我有
if( SIZE_MAX/2/sizeof(T) < n )
首先,取SIZE_MAX的一半来解决理由中提到的问题;但后来我意识到如果sizeof(T) 为1,我们只需要一半SIZE_MAX。鉴于此代码,第二个版本(肯定是严格符合的)似乎一点也不差。但是,如果我是对的,我还是很感兴趣。
C11 保留 §6.5.6 (9) 的措辞,也欢迎对该主题的 C++ 相关答案。
【问题讨论】:
-
标准中的数字似乎非常适合具有分段内存的 16 位系统:每个段为 64kB 大,并且数组必须适合一个段,因此最大差异为 +/- 64k。但是,远指针需要 32 位(段选择器加上偏移量)。