在索引数组时,我是否应该总是使用size_t?

19

即使数组的大小没有超过int的大小,我是否需要始终使用size_t来索引数组?

这不是关于何时应该使用size_t的问题。我只想知道,例如,一个程序有2GB可用内存(所有这些字段都可以通过int32进行索引),但是该内存被分配到计算机RAM的14GB - 16GB的"fields"。

如果在这种情况下使用int32而不是size_t(或unsigned long int)进行索引,它是否总是会失败?

也许这个问题更与虚拟内存有关,而不是指针。


1
对数组进行索引与主机的 RAM 无关。鼓励使用 size_t 的原因是它使您的程序具有可移植性。 - Mukul Gupta
2
size_t 确保能够索引您所投掷的任何内容的每个字节。有时很方便。 - user4581301
6
你可以使用足够大的整数类型来存储数组的索引。对于小型数组,char 是可行的选择。这与可寻址内存或虚拟内存的总量无关。如果您不知道数组大小,size_t 能够容纳任何数组的任何索引,因此非常有用。 - n. m.
@user4581301:C标准没有提供这样的保证。 - Eric Postpischil
@EricPostpischil: 它确实可以。具体来说,它可以容纳最大可能数组的大小,因为数组是一种对象类型。因此,如果您可以对其进行索引,则大小(以字节为单位)适合于size_t。由于数组元素至少为一个字节,因此每个索引也是如此。混淆可能是因为不能保证size_t容纳多个对象大小的总和。 - MSalters
显示剩余2条评论
3个回答

17

size_t是一个无符号整数,能够保存您可以分配的最大对象的大小。它非常有用于索引,因为这意味着它可以索引到您可以分配的最大数组。

但这并不意味着索引时必须或者一定建议使用它。您可以使用任何足够大的整数类型来对数组进行索引。int_fast32_t 可能更快,uint_least16_t 在结构中可能更小,等等。了解您的数据,您可以做出一个好的选择。

您应该考虑的一个问题是,在某些平台上,使用带符号索引可能需要额外的符号扩展指令。例如,这里是x86-64:

// ; zero-extending idx (in edx) is "free" by simply using rdx.
// movzx eax, BYTE PTR [rcx+rdx]
// ret
char get_index(char *ptr, unsigned idx)
{
   return ptr[idx];
}

// ; sign extending idx from 32 bits to 64 bits with movsx here.
// movsx rdx, edx     
// movzx eax, BYTE PTR [rcx+rdx]
// ret
char get_index(char *ptr, int idx)
{
   return ptr[idx];
}

虚拟内存超出了C或C++的范围。从它们的角度来看,你只是索引到内存中,由你的平台来使其工作。实际上,你的应用程序只使用虚拟地址;你的CPU/操作系统在幕后将虚拟地址转换为物理地址。这不是你需要担心的事情。


2
即使数组不足以超过 int 的大小,我需要始终使用 size_t 来索引吗?
不需要,因为索引不是地址,甚至不是字节偏移量。它是用于从指针计算偏移量的值。只要整数类型足够大以表示最大索引,就可以访问数组中的任何元素。
话虽如此,在数组索引的上下文中,有一些关于不同类型处理的细节。虽然这个问题标记了 C 和 C++,并且虽然两者有很多相似之处,但在 C++ 中可能会出现一些额外的复杂情况。但我们先从共同点开始。
为了简化这个解释,假设您拥有一个具有 4GB 地址空间和 16 位 int 的机器。
int a[8000];

显然,这声明了一个由8000个int组成的数组,索引从0到7999。编译器将8000文字面量视为int类型,因此不会有任何意外。
但是,如果文字面量为0x8000,则情况会变得更加复杂。由于16位int无法表示32768(0x8000的十进制等价物),它必须选择另一种类型,如long。但是,信不信由你,如果文字面量用十六进制表示,它会选择unsigned int。 (文字面量的规则与整数提升的规则并不完全相同。)这在这里实际上并不重要,因为净效果是相同的。
请注意,如果我们使用0x8000,我们将创建一个可以完全使用16位int进行索引的数组,但我们必须使用不同的类型来表示其大小。
在C中进行索引是古怪的,而C ++继承了这种古怪性。这些语言不关心您是否交换了数组的索引和名称。
a[3] = 42;
printf("%d\n", 3[a]);  // legal, and prints "42"!

您甚至可以从指向数组的指针进行索引,并使用负下标(只要引用仍在同一数组或其末尾的一个元素内)。

a[3] = 42;
int *p = a + 6;
printf("%d\n", p[-3]);  // also legal, and prints "42"!

// off-topic
printf("%d\n", -3[p]);  // legal, but does *not* print "42"!

这告诉我们索引可以是有符号整数类型,但是 size_t 总是无符号整数类型。
以上所有内容也适用于 C++,但是如果您考虑标准库容器类型,如 std::vectorstd::array,C++ 有一些额外的细节。
标准容器定义了自己的大小类型,例如 std::vector::size_type。这些几乎总是与 std::size_t 相同的底层类型。
标准容器只是在库中定义的类,因此古怪的交换指针和索引语法不适用于它们。
std::vector<int> v = { 0, 1, 2, 3 };
std::cout << 3[v] << '\n';  // std container types cannot emulate this
                            // kind of array indexing

那么索引的类型必须是什么?我们知道可以这样写:
```python indexes = [0, 1, 2, 3, 4] ```
所以,索引的类型应该是整数。
std::cout << v[3] << '\n';

我们知道,字面量3被视为一个int,它是有符号的且可能具有比std::size_t -- 呃,std::vector<int>::size_type更小的范围。希望这使得清楚,只要您使用的任何类型可以表示所需的索引,就不需要使用大小类型作为索引。
使用标准容器类型进行索引是通过在容器的大小类型上重载operator[]实现的。因此,3可能是一个int,但是为了匹配重载的定义,它将首先转换为std::vector<int>::size_type
换句话说,对于C++标准容器类型,您实际上总是使用size_t作为索引,而不管实际使用的类型是什么。

1
为了避免程序故障,程序员应始终使用至少与size()方法返回类型一样大的索引类型。这确保了索引永远不会超出数组可能的大小。数组的实现通常是确保其运行时大小不会超过size()方法返回的类型。这意味着索引类型应该是:
  • char[N], uint8_t[N], int[N]等情况下,使用size_t
  • std::vectorstd::list情况下,使用size_t
  • QListQVector情况下,使用int
  • 在位数组的情况下,如果位数组的size()方法返回一个任意精度整数(aint),则使用aint
  • 在内存压缩的数组的情况下,如果数组的size()方法返回一个任意精度整数(aint),则使用aint
  • 在跨多台机器的数组的情况下,如果数组的size()方法返回一个任意精度整数(aint),则使用aint
  • 其他语言:
    • java.util.Collection及其子类的情况下,使用int

总之:安全的索引类型是size()方法返回的类型。

注意:如果size()方法返回无符号的size_t,那么有符号的intssize_t不是安全的索引类型。在gcc和clang中,编译器标志-Wsign-compare(通过-Wall启用)和-Wconversion可以用于防止大多数这种情况。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接