索引数组时我应该总是使用 size_t 吗?
Should I always use size_t when indexing arrays?
是否需要在索引数组时始终使用 size_t
,即使数组不够大,无法超过 int 的大小?
这不是什么时候应该使用 size_t
的问题。我只想知道,例如,一个程序是否有 2GB 的可用内存(所有这些字段都可以用 int32 索引)但是这个内存(虚拟内存)分配给 "fields" 14GB - 16GB计算机的内存。
在这种情况下,如果我使用 int32
而不是 size_t
(或 unsigned long int
),索引内存时是否总是会失败?
也许问题更多的是关于虚拟内存而不是指针。
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/OS 正在幕后将虚拟地址转换为物理地址。这不是您需要担心的事情。
为了避免程序失败,程序员应该始终使用至少与 size()
方法返回的类型一样大的索引类型。这确保索引永远不会溢出数组的任何可能大小。数组的实现通常确保其运行时大小永远不会溢出 size()
方法返回的类型。这意味着索引类型应该是:
size_t
在 char[N]
、uint8_t[N]
、int[N]
等情况下
size_t
在 std::vector
和 std::list
的情况下
int
在 QList
和 QVector
的情况下
- 位数组情况下的任意精度整数 (aint)(如果位数组的
size()
方法 returns 是 aint)
- 如果数组在内存中压缩(如果数组的
size()
方法 returns 是 aint)
- aint 在数组跨越多台机器的情况下(如果数组的
size()
方法 returns aint)
- C++以外的其他语言:
int
在 java.util.Collection
及其子类的情况下
总结:安全的索引类型是size()
方法返回的类型。
注意:如果 size()
方法 returns 无符号 size_t
,则有符号 int
和 ssize_t
不是安全索引类型。对于 gcc 和 clang,编译器标志 -Wsign-compare
(由 -Wall
启用)和 -Wconversion
可用于防止大多数此类情况。
Do I need to use size_t
always when indexing an array even if the array is not big enough to exceed the size of an int
?
否,因为索引不是地址,甚至不是字节偏移量。它是一个用于计算指针偏移量的值。只要整数类型足够大,可以表示最大的索引,那么你就可以访问数组中的任何元素。
也就是说,有一些关于如何在数组索引的上下文中处理不同类型的细节。这个问题用 C 和 C++ 标记,虽然有很多相似之处,但 C++ 可以发挥一些额外的作用。但是让我们从共同的部分开始吧。
为了简化解释,我们假设您有一台具有 4GB 地址 space 和 16 位 int
s 的机器。
int a[8000];
显然,这声明了一个包含 8000 个 int
的数组,索引从 0 到 7999。编译器将 8000
文字视为 int
,因此没有真正的惊喜.
但是如果字面意思是 0x8000
,它会变得更复杂。由于 16 位 int
不能表示 32768(0x8000 的十进制等效值),因此它必须选择不同的类型,例如 long
。但是,不管你信不信,如果文字是以十六进制表示的,它会选择 unsigned int
代替。 (文字规则与整数提升规则不太一样。)这里实际上并不重要,因为净效果是相同的。
请注意,如果我们使用 0x8000,我们将创建一个可以使用 16 位 int
完全 索引 的数组,但我们有使用不同的类型来表示其 size.
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::vector
和 std::array
.
等标准库容器类型,C++ 还有一些额外的问题
标准容器定义了自己的尺寸类型,如 std::vector::size_type
。这些几乎总是与 std::size_t
.
相同的基础类型
标准容器只是 类 在库中定义的,所以古怪的 swap-pointer-and-index 语法不适用于它们。
std::vector<int> v = { 0, 1, 2, 3 };
std::cout << 3[v] << '\n'; // std container types cannot emulate this
// kind of array indexing
那么索引必须是什么类型?好吧,我们知道这样写是合法的:
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
作为索引。
是否需要在索引数组时始终使用 size_t
,即使数组不够大,无法超过 int 的大小?
这不是什么时候应该使用 size_t
的问题。我只想知道,例如,一个程序是否有 2GB 的可用内存(所有这些字段都可以用 int32 索引)但是这个内存(虚拟内存)分配给 "fields" 14GB - 16GB计算机的内存。
在这种情况下,如果我使用 int32
而不是 size_t
(或 unsigned long int
),索引内存时是否总是会失败?
也许问题更多的是关于虚拟内存而不是指针。
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/OS 正在幕后将虚拟地址转换为物理地址。这不是您需要担心的事情。
为了避免程序失败,程序员应该始终使用至少与 size()
方法返回的类型一样大的索引类型。这确保索引永远不会溢出数组的任何可能大小。数组的实现通常确保其运行时大小永远不会溢出 size()
方法返回的类型。这意味着索引类型应该是:
size_t
在char[N]
、uint8_t[N]
、int[N]
等情况下size_t
在std::vector
和std::list
的情况下
int
在QList
和QVector
的情况下
- 位数组情况下的任意精度整数 (aint)(如果位数组的
size()
方法 returns 是 aint) - 如果数组在内存中压缩(如果数组的
size()
方法 returns 是 aint) - aint 在数组跨越多台机器的情况下(如果数组的
size()
方法 returns aint) - C++以外的其他语言:
int
在java.util.Collection
及其子类的情况下
总结:安全的索引类型是size()
方法返回的类型。
注意:如果 size()
方法 returns 无符号 size_t
,则有符号 int
和 ssize_t
不是安全索引类型。对于 gcc 和 clang,编译器标志 -Wsign-compare
(由 -Wall
启用)和 -Wconversion
可用于防止大多数此类情况。
Do I need to use
size_t
always when indexing an array even if the array is not big enough to exceed the size of anint
?
否,因为索引不是地址,甚至不是字节偏移量。它是一个用于计算指针偏移量的值。只要整数类型足够大,可以表示最大的索引,那么你就可以访问数组中的任何元素。
也就是说,有一些关于如何在数组索引的上下文中处理不同类型的细节。这个问题用 C 和 C++ 标记,虽然有很多相似之处,但 C++ 可以发挥一些额外的作用。但是让我们从共同的部分开始吧。
为了简化解释,我们假设您有一台具有 4GB 地址 space 和 16 位 int
s 的机器。
int a[8000];
显然,这声明了一个包含 8000 个 int
的数组,索引从 0 到 7999。编译器将 8000
文字视为 int
,因此没有真正的惊喜.
但是如果字面意思是 0x8000
,它会变得更复杂。由于 16 位 int
不能表示 32768(0x8000 的十进制等效值),因此它必须选择不同的类型,例如 long
。但是,不管你信不信,如果文字是以十六进制表示的,它会选择 unsigned int
代替。 (文字规则与整数提升规则不太一样。)这里实际上并不重要,因为净效果是相同的。
请注意,如果我们使用 0x8000,我们将创建一个可以使用 16 位 int
完全 索引 的数组,但我们有使用不同的类型来表示其 size.
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::vector
和 std::array
.
标准容器定义了自己的尺寸类型,如 std::vector::size_type
。这些几乎总是与 std::size_t
.
标准容器只是 类 在库中定义的,所以古怪的 swap-pointer-and-index 语法不适用于它们。
std::vector<int> v = { 0, 1, 2, 3 };
std::cout << 3[v] << '\n'; // std container types cannot emulate this
// kind of array indexing
那么索引必须是什么类型?好吧,我们知道这样写是合法的:
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
作为索引。