C中数组索引的正确类型是什么?

时间:2010-07-04 13:41:42

标签: c types indexing c99

应该使用C99中数组索引的类型?它必须适用于LP32,ILP32,ILP64,LP64,LLP64等。它不一定是C89类型。

我找到了5位候选人:

  • size_t
  • ptrdiff_t
  • intptr_t / uintptr_t
  • int_fast*_t / uint_fast*_t
  • int_least*_t / uint_least*_t

有更简单的代码可以更好地说明问题。这两个特定循环中ij的最佳类型是什么。如果有充分的理由,两种不同的类型也可以。

for (i=0; i<imax; i++) {
        do_something(a[i]);
}
/* jmin can be less than 0 */
for (j=jmin; j<jmax; j++) {
        do_something(a[j]);
}

P.S。 在问题的第一个版本中,我忘记了负面索引。

P.P.S。 我不打算编写C99编译器。但是编译器程序员的任何答案对我来说都是非常有价值的。

类似的问题:

9 个答案:

答案 0 :(得分:34)

我认为您应该使用ptrdiff_t,原因如下

  • 指数可能是负数(因此所有无符号类型,包括size_t都是不可能的)
  • p2 - p1的类型为ptrdiff_t。相反的i类型*(p1 + i)也应该是那种类型(注意*(p + i)等同于p[i]

答案 1 :(得分:15)

我几乎总是将size_t用于数组索引/循环计数器。当然,在某些特殊情况下您可能需要签名偏移,但通常使用签名类型会遇到很多问题:

最大的风险是,如果您通过调用者传递巨大的大小/偏移量来处理未签名的内容(或者如果您从错误信任的文件中读取它),您可能会将其解释为负数并且无法抓住它的界限。例如,if (offset<size) array[offset]=foo; else error();将写入不应该写的地方。

另一个问题是带有符号整数溢出的未定义行为的可能性。无论你使用无符号算术还是带符号算术,都需要注意和检查溢出问题,但我个人觉得无符号行为更易于处理。

使用无符号算术的另一个原因(通常) - 有时我使用索引作为位数的偏移量,我想使用%8和/ 8或%32和/ 32。对于签名类型,这些将是实际的除法操作。使用无符号时,可以生成预期的按位和/位移操作。

答案 2 :(得分:12)

由于sizeof(array)(和malloc的参数)的类型是size_t,并且数组不能容纳比其大小更多的元素,因此它遵循{{1} }可以用于数组的索引。

修改 此分析适用于基于0的数组,这是常见情况。 size_t在任何情况下都可以工作,但是索引变量有一个指针差异类型有点奇怪。

答案 3 :(得分:6)

如果您从0开始,请使用 size_t ,因为该类型必须能够索引任何数组:

  • sizeof会返回它,因此对于数组而言,它不能超过size_t个元素
  • malloc将其作为参数,如Amnon
  • 所述

如果你从零开始,那么转移到零开始,并使用size_t,由于上述原因,保证可以正常工作。所以替换:

for (j = jmin; j < jmax; j++) {
    do_something(a[j]);
}

使用:

int *b = &a[jmin];
for (size_t i = 0; i < (jmax - jmin); i++) {
    do_something(b[i]);
}

为什么使用:

  • ptrdiff_t :此代表的最大值可能小于size_t的最大值。

    提到at cppref,如果数组太大,可能会出现未定义的行为,建议在C99 6.5.5 / 9:

      

    当减去两个指针时,两个指针都指向同一个数组对象的元素,   或者超过数组对象的最后一个元素;结果是差异   两个数组元素的下标。结果的大小是实现定义的,   它的类型(有符号整数类型)是标题中定义的ptrdiff_t。   如果结果在该类型的对象中无法表示,则行为未定义

    出于好奇,intptr_t在分段内存架构上也可能大于size_thttps://stackoverflow.com/a/1464194/895245

    GCC还对静态数组对象的最大大小施加了进一步的限制:What is the maximum size of an array in C?

  • uintptr_t :我不确定。所以我只使用size_t,因为我更确定: - )

答案 4 :(得分:1)

如果您事先知道阵列的最大长度,可以使用

  • int_fast*_t / uint_fast*_t
  • int_least*_t / uint_least*_t

在所有其他情况下,我建议使用

  • size_t

  • ptrdiff_t

取决于你想要允许负指数的天气。

使用

  • intptr_t / uintptr_t

也会安全,但语义有点不同。

答案 5 :(得分:1)

在您的情况下,我会使用ptrdiff_t。这不仅仅是指标可能是负面的。你可能想要倒数到零,在这种情况下,签名类型会产生一个讨厌的,微妙的错误:

for(size_t i=5; i>=0; i--) {
  printf("danger, this loops forever\n);
}

如果您使用ptrdiff_t或任何其他合适的签名类型,则不会发生这种情况。在POSIX系统上,您可以使用ssize_t

就个人而言,我经常只使用int,即使它可能不是正确的事情。

答案 6 :(得分:1)

我使用unsigned int。 (虽然我更喜欢速记unsigned

在C99中,unsigned int保证能够索引任何可移植阵列。只保证支持65'535字节或更小的数组,最大unsigned int值至少为65'535。

来自公众WG14 N1256的C99标准草案:

  

5.2.4.1翻译限制

     

实现应能够翻译和执行至少一个包含以下每个限制的至少一个实例的程序:(实现应尽可能避免强加固定的翻译限制。)

     

(...)

     
      
  • 对象中的65535个字节(仅限托管环境中)
  •   
     

(...)

     

5.2.4.2数值限制

     

需要一个实现来记录本子条款中指定的所有限制,这些限制在标题<limits.h><float.h>中指定。 <stdint.h>中指定了其他限制。

     

5.2.4.2.1整数类型<limits.h>

的大小      

下面给出的值应替换为适用于#if预处理指令的常量表达式。此外,除CHAR_BITMB_LEN_MAX之外,以下内容应替换为与表达式相同的表达式,该表达式是根据整数提升转换的相应类型的对象。它们的实现定义值的大小应相等或更大(绝对值v   alue)显示的那些,具有相同的符号。

     

(...)

     
      
  • unsigned int UINT_MAX类型的对象的最大值65535 // 2 ^ 16 - 1
  •   

在C89中,最大可移植阵列大小实际上只有32'767字节,所以即使是签名的int也会这样做,其最大值至少为32'767(附录A.4)。 / p>

来自C89草案的§2.2.4:

  

2.2.4.1翻译限制

     

实现应能够翻译和执行至少一个包含以下每个限制的至少一个实例的程序:(实现应尽可能避免强加固定的翻译限制。)

     

(...)

     
      
  • 对象中的32767字节(仅限托管环境中)
  •   
     

(...)

     

2.2.4.2数值限制

     

符合要求的实施应记录本节规定的所有限制,这些限制应在标题<limits.h><float.h>中指定。

     

“整数类型的大小<limits.h>

     

下面给出的值应替换为适用于#if预处理指令的常量表达式。它们的实现定义值的大小(绝对值)应等于或大于显示的值,具有相同的符号。

     

(...)

     
      
  • int INT_MAX +32767
  • 类型的对象的最大值   

答案 7 :(得分:1)

我的选择: ptrdiff_t

许多人都投票支持ptrdiff_t,但是有些人说使用指针差异类型进行索引很奇怪。对我来说,这很合理:数组索引是与原始指针的区别。

有些人还说size_t是正确的,因为这样做是为了保留大小。但是,正如某些人评论的那样:这是字节大小,因此通常可以保存比最大可能数组索引大几倍的值。

答案 8 :(得分:1)

我通常使用size_t表示数组偏移量,但是如果要使用负数组索引,请使用int。它能够解决C89保证的最大大小数组(32767字节)。

如果要访问C99保证的最大大小的数组(65535字节),请使用unsigned

有关C允许但不保证访问数组的信息,请参见以前的版本。