我对 C 语言感到困惑size_t
。我知道它是由sizeof
操作员返回的。但它到底是什么?它是一种数据类型吗?
假设我有一个for
循环:
for(i = 0; i < some_size; i++)
我应该使用int i;
orsize_t i;
吗?
根据 1999 ISO C 标准 (C99),
size_t
它是至少 16 位的无符号整数类型(参见第 7.17 和 7.18.3 节)。
size_t
是由多个 C/C++ 标准定义的无符号数据类型,例如 C99 ISO/IEC 9899 标准,在stddef.h
. 1它可以通过包含进一步导入,stdlib.h
因为该文件内部子包含stddef.h
.此类型用于表示对象的大小。采用或返回大小的库函数期望它们的类型或返回类型为
size_t
. 此外,最常用的基于编译器的运算符 sizeof 应计算为与 兼容的常量值size_t
。
作为暗示,size_t
是一种保证保存任何数组索引的类型。
size_t
是无符号类型。因此,它不能代表任何负值(<0)。你在计算某物时使用它,并确定它不能为负数。例如,strlen()
返回 asize_t
因为字符串的长度必须至少为 0。
在您的示例中,如果您的循环索引将始终大于 0,则使用size_t
或任何其他无符号数据类型可能是有意义的。
当你使用一个size_t
对象时,你必须确保在所有使用它的上下文中,包括算术,你需要非负值。例如,假设您有:
size_t s1 = strlen(str1);
size_t s2 = strlen(str2);
并且您想找出 和 的长度之str2
差str1
。你不能这样做:
int diff = s2 - s1; /* bad */
这是因为分配给的值diff
总是一个正数,即使是s2 < s1
,因为计算是使用无符号类型完成的。在这种情况下,根据您的用例,您最好使用int
(或long long
) for s1
and s2
。
C/POSIX 中有一些函数可以/应该使用size_t
,但由于历史原因,不要使用。例如,理想情况下,第二个参数fgets
应该是size_t
,但是是int
。
size_t
是一种可以保存任何数组索引的类型。
根据实现,它可以是以下任何一种:
unsigned char
unsigned short
unsigned int
unsigned long
unsigned long long
这是我的机器size_t
中的定义方式:stddef.h
typedef unsigned long size_t;
如果你是经验型,
echo | gcc -E -xc -include 'stddef.h' - | grep size_t
Ubuntu 14.04 64 位 GCC 4.8 的输出:
typedef long unsigned int size_t;
请注意,这stddef.h
是由 GCC 提供的,而不是src/gcc/ginclude/stddef.h
GCC 4.2 中的 glibc。
有趣的 C99 出场
malloc
接受size_t
作为参数,因此它确定可以分配的最大大小。
并且由于它也由 返回sizeof
,我认为它限制了任何数组的最大大小。
另请参阅:C 中数组的最大大小是多少?
types.h的联机帮助页说:
size_t 应为无符号整数类型
要了解为什么size_t
需要存在以及我们如何到达这里:
在实用方面,size_t
并且ptrdiff_t
保证在 64 位实现上为 64 位宽,在 32 位实现上保证为 32 位宽,依此类推。他们不能在不破坏遗留代码的情况下,在每个编译器上强制任何现有类型都意味着这一点。
A size_t
orptrdiff_t
不一定与intptr_t
or相同uintptr_t
。它们在某些体系结构上有所不同,这些体系结构在 1980 年代后期被添加到标准时仍在使用,size_t
并且在C99添加了许多新类型但尚未消失(例如 16 位 Windows)时变得过时。16 位保护模式下的 x86 有一个分段内存,其中最大可能的数组或结构可能只有 65,536 字节大小,但指针需要 32 位宽,比寄存器宽。在那些上,应该是 32 位宽,但是ptrdiff_t
far
intptr_t
size_t
ptrdiff_t
可以是 16 位宽并适合寄存器。谁知道将来会写出什么样的操作系统?理论上,i386 架构提供了一个 32 位分段模型和 48 位指针,没有操作系统实际使用过。
内存偏移的类型不可能是long
因为太多的遗留代码假定它long
正好是 32 位宽。这一假设甚至内置于 UNIX 和 Windows API。不幸的是,许多其他遗留代码也假设 along
的宽度足以容纳指针、文件偏移量、自 1970 年以来经过的秒数等等。POSIX 现在提供了一种标准化的方法来强制后者假设为真,而不是前者,但两者都不是一个可移植的假设。
这不可能是int
因为在 90 年代只有极少数编译器能实现int
64 位宽。long
然后通过保持32 位宽,它们真的变得很奇怪。该标准的下一个修订版宣布int
宽于是非法的long
,但int
在大多数 64 位系统上仍然是 32 位宽。
它不可能是long long int
,无论如何都是后来添加的,因为即使在 32 位系统上,它也被创建为至少 64 位宽。
因此,需要一种新类型。即使不是这样,所有其他类型也意味着数组或对象中的偏移量以外的东西。如果从 32 位到 64 位迁移的惨败中有一个教训,那就是具体说明一种类型需要具有哪些属性,而不是使用在不同程序中意味着不同事物的属性。
由于还没有人提到它,它的主要语言意义size_t
是sizeof
运算符返回该类型的值。同样, 的主要意义ptrdiff_t
是从另一个指针中减去一个指针将产生该类型的值。接受它的库函数这样做是因为它允许此类函数在可能存在此类对象的系统上处理大小超过 UINT_MAX 的对象,而不会强制调用者浪费代码在较大类型的系统上传递大于“unsigned int”的值对于所有可能的对象就足够了。
size_t
并且int
不可互换。例如,在 64 位 Linuxsize_t
上,大小为 64 位(即sizeof(void*)
),但int
为 32 位。
另请注意,size_t
未签名。如果您需要签名版本,那么ssize_t
在某些平台上有,它与您的示例更相关。
作为一般规则,我建议int
在大多数一般情况下使用,并且仅在有特定需要时使用size_t
/ (例如)。ssize_t
mmap()
size_t
是一种无符号整数数据类型,只能分配 0 和大于 0 的整数值。它测量任何对象大小的字节并由sizeof
运算符返回。
const
是 的语法表示size_t
,但没有const
你可以运行程序。
const size_t number;
size_t
经常用于数组索引和循环计数。如果编译器是32-bit
它会在unsigned int
. 如果编译器是64-bit
它也可以工作unsigned long long int
。最大大小size_t
取决于编译器类型。
size_t
已在<stdio.h>
头文件中定义,但也可以由
<stddef.h>
、<stdlib.h>
、<string.h>
、<time.h>
和<wchar.h>
头文件定义。
const
)#include <stdio.h>
int main()
{
const size_t value = 200;
size_t i;
int arr[value];
for (i = 0 ; i < value ; ++i)
{
arr[i] = i;
}
size_t size = sizeof(arr);
printf("size = %zu\n", size);
}
输出: size = 800
const
)#include <stdio.h>
int main()
{
size_t value = 200;
size_t i;
int arr[value];
for (i = 0; i < value; ++i)
{
arr[i] = i;
}
size_t size = sizeof(arr);
printf("size = %zu\n", size);
}
输出: size = 800
size_t 是无符号整数数据类型。在使用 GNU C 库的系统上,这将是 unsigned int 或 unsigned long int。size_t 通常用于数组索引和循环计数。
一般来说,如果您从 0 开始向上,请始终使用无符号类型以避免溢出导致您陷入负值情况。这一点非常重要,因为如果您的数组边界恰好小于循环的最大值,但您的循环最大值恰好大于您的类型的最大值,您将环绕负数并且您可能会遇到分段错误(SIGSEGV )。所以,一般来说,永远不要将 int 用于从 0 开始并向上的循环。使用无符号。
size_t或任何无符号类型可能被视为循环变量,因为循环变量通常大于或等于 0。
当我们使用size_t对象时,我们必须确保在所有使用它的上下文中,包括算术,我们只需要非负值。例如,下面的程序肯定会给出意想不到的结果:
// C program to demonstrate that size_t or
// any unsigned int type should be used
// carefully when used in a loop
#include<stdio.h>
int main()
{
const size_t N = 10;
int a[N];
// This is fine
for (size_t n = 0; n < N; ++n)
a[n] = n;
// But reverse cycles are tricky for unsigned
// types as can lead to infinite loop
for (size_t n = N-1; n >= 0; --n)
printf("%d ", a[n]);
}
Output
Infinite loop and then segmentation fault
这是特定于平台的typedef
. 例如,在特定机器上,它可能是unsigned int
或unsigned long
。您应该使用此定义来提高代码的可移植性。
size_t
是一个 typedef,用于表示任何对象的大小(以字节为单位)。(Typedef 用于为另一种数据类型创建附加名称/别名,但不会创建新类型。)
找到它定义stddef.h
如下:
typedef unsigned long long size_t;
size_t
中也有定义<stdio.h>
。
size_t
被 sizeof 运算符用作返回类型。
使用size_t
与 sizeof 一起定义数组大小参数的数据类型,如下所示:
#include <stdio.h>
void disp_ary(int *ary, size_t ary_size)
{
for (int i = 0; i < ary_size; i++)
{
printf("%d ", ary[i]);
}
}
int main(void)
{
int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
int ary_size = sizeof(arr)/sizeof(int);
disp_ary(arr, ary_size);
return 0;
}
size_t
保证足够大以包含主机系统可以处理的最大对象的大小。
请注意,数组的大小限制实际上是编译和执行此代码的系统堆栈大小限制的一个因素。您应该能够在链接时调整堆栈大小(请参阅ld
命令的 --stack-size
参数)。
为了让您大致了解堆栈大小:
许多 C 库函数都喜欢malloc
,memcpy
并将strlen
它们的参数和返回类型声明为size_t
.
size_t
通过添加/减去所需元素的数量而不是使用以字节为单位的偏移量,程序员能够处理不同的类型。
size_t
让我们通过检查它在 C 字符串和整数数组的指针算术运算中的用法来更深入地了解它可以为我们做什么:
下面是一个使用 C 字符串的示例:
const char* reverse(char *orig)
{
size_t len = strlen(orig);
char *rev = orig + len - 1;
while (rev >= orig)
{
printf("%c", *rev);
rev = rev - 1; // <= See below
}
return rev;
}
int main() {
char *string = "123";
printf("%c", reverse(string));
}
// Output: 321
0x7ff626939004 "123" // <= orig
0x7ff626939006 "3" // <= rev - 1 of 3
0x7ff626939005 "23" // <= rev - 2 of 3
0x7ff626939004 "123" // <= rev - 3 of 3
0x7ff6aade9003 "" // <= rev is indeterminant. This can be exploited as an out of bounds bug to read memory contents that this program has no business reading.
这对于理解使用的好处不是很有帮助,size_t
因为一个字符是一个字节,无论您的架构如何。
当我们处理数值类型时,它size_t
变得非常有益。
size_t
类型就像一个整数,具有可以保存物理内存地址的好处;该地址根据执行它的平台类型更改其大小。
以下是我们在传递整数数组时如何利用 sizeof 和 size_t 的方法:
void print_reverse(int *orig, size_t ary_size)
{
int *rev = orig + ary_size - 1;
while (rev >= orig)
{
printf("%i", *rev);
rev = rev - 1;
}
}
int main()
{
int nums[] = {1, 2, 3};
print_reverse(nums, sizeof(nums)/sizeof(*nums));
return 0;
}
0x617d3ffb44 1 // <= orig
0x617d3ffb4c 3 // <= rev - 1 of 3
0x617d3ffb48 2 // <= rev - 2 of 3
0x617d3ffb44 1 // <= rev - 3 of 3
上面,我们看到一个 int 占用 4 个字节(因为每个字节有 8 位,所以一个 int 占用 32 位)。
如果我们要创建一个 long 数组,我们会发现 long 在 linux64 操作系统上需要 64 位,但在 Win64 系统上只需要 32 位。因此,使用t_size
, 将节省大量编码和潜在错误,尤其是在运行在不同架构上执行地址算术的 C 代码时。
所以这个故事的寓意是“使用size_t
并让你的 C 编译器完成指针运算中容易出错的工作”。
据我了解,size_t
是一个unsigned
整数,其位大小足以容纳本机架构的指针。
所以:
sizeof(size_t) >= sizeof(void*)