我对 C 中的 size_t
感到困惑。我知道它是由 sizeof
运算符返回的。但它到底是什么?它是一种数据类型吗?
假设我有一个 for
循环:
for(i = 0; i < some_size; i++)
我应该使用 int i;
还是 size_t i;
?
some_size
已签名,则使用 int
,如果未签名,则使用 size_t
。
int i
可能不足以处理一个巨大的数组。因此,通过使用 size_t i
您可以处理更多索引,因此即使您有一个庞大的数组也不应该成为问题。 size_t
是一种数据类型:通常是 unsigned long int
,但这取决于您的系统。
根据 1999 ISO C 标准 (C99),size_t 是至少 16 位的无符号整数类型(参见第 7.17 和 7.18.3 节)。 size_是由几个 C/C++ 标准定义的无符号数据类型,例如 C99 ISO/IEC 9899 标准,在 stddef.h.1 中定义。它可以通过包含 stdlib.h 进一步导入,因为该文件内部子包含 stddef。 H。此类型用于表示对象的大小。采用或返回大小的库函数期望它们是类型或返回类型为 size_t。此外,最常用的基于编译器的运算符 sizeof 应计算为与 size_t 兼容的常量值。
作为暗示,size_t
是一种保证保存任何数组索引的类型。
size_t
是无符号类型。因此,它不能表示任何负值(<0)。你在计算某物时使用它,并确定它不能为负数。例如,strlen()
返回 size_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
)用于 s1
和 s2
。
C/POSIX 中有一些函数可以/应该使用 size_t
,但由于历史原因不能使用。例如,fgets
的第二个参数理想情况下应该是 size_t
,但实际上是 int
。
size_t
的大小是 sizeof(size_t)
。 C 标准保证 SIZE_MAX
至少为 65535。size_t
是 sizeof
运算符返回的类型,在标准库中使用(例如 strlen
返回 size_t
)。正如 Brendan 所说,size_t
不必与 unsigned int
相同。
size_t
保证是无符号类型。
s2 - s1
的值溢出 int
,则行为未定义。
size_t
是一种可以保存任何数组索引的类型。
根据实现,它可以是以下任何一种:
unsigned char
unsigned short
unsigned int
unsigned long
unsigned long long
以下是在我的机器的 stddef.h
中定义 size_t
的方式:
typedef unsigned long size_t;
unsigned long
是 32 位,size_t
是 64 位。
size_t
在 32 位机器上总是 32 位,同样是 64 位,这是真的吗?
unsigned char
?
uint_least16_t
是至少 16 位。关于,size_t
,标准说“sizeof 运算符结果的无符号整数类型”和“sizeof 运算符产生其操作数的大小(以字节为单位)”。
unsigned char
不能是 16 位?!
如果你是经验型,
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 提供,而不是 GCC 4.2 中 src/gcc/ginclude/stddef.h
下的 glibc。
有趣的 C99 出场
malloc 将 size_t 作为参数,因此它确定可以分配的最大大小。而且由于它也是由 sizeof 返回的,我认为它限制了任何数组的最大大小。另请参阅:C 中数组的最大大小是多少?
要了解为什么需要存在 size_t
以及我们是如何到达这里的:
在实用方面,size_t
和 ptrdiff_t
在 64 位实现上保证为 64 位宽,在 32 位实现上保证为 32 位宽,依此类推。他们不能在不破坏遗留代码的情况下,在每个编译器上强制任何现有类型都意味着这一点。
size_t
或 ptrdiff_t
不一定与 intptr_t
或 uintptr_t
相同。它们在某些体系结构上有所不同,这些体系结构在 1980 年代后期将 size_t
和 ptrdiff_t
添加到标准时仍在使用,而当 C99 添加了许多新类型但尚未消失(例如 16 位视窗)。 16 位保护模式下的 x86 有一个分段内存,其中最大可能的数组或结构可能只有 65,536 字节大小,但 far
指针需要 32 位宽,比寄存器宽。在那些上,intptr_t
将是 32 位宽,但 size_t
和 ptrdiff_t
可能是 16 位宽并适合寄存器。谁知道将来会写出什么样的操作系统?理论上,i386 架构提供了一个 32 位分段模型和 48 位指针,没有操作系统实际使用过。
内存偏移的类型不能是 long
,因为太多的旧代码假定 long
正好是 32 位宽。这一假设甚至内置于 UNIX 和 Windows API。不幸的是,许多其他遗留代码也假设 long
的宽度足以容纳指针、文件偏移量、自 1970 年以来经过的秒数等等。 POSIX 现在提供了一种标准化的方法来强制后者假设为真,而不是前者,但两者都不是一个可移植的假设。
它不可能是 int
,因为在 90 年代只有极少数编译器将 int
设为 64 位宽。然后通过保持 long
32 位宽,他们真的变得很奇怪。该标准的下一个修订版宣布 int
比 long
宽是非法的,但在大多数 64 位系统上 int
仍然是 32 位宽。
它不可能是 long long int
,它是后来添加的,因为即使在 32 位系统上,它也被创建为至少 64 位宽。
因此,需要一种新类型。即使不是这样,所有其他类型也意味着数组或对象中的偏移量以外的东西。如果从 32 位到 64 位迁移的惨败中有一个教训,那就是具体说明一种类型需要具有哪些属性,而不是使用在不同程序中意味着不同事物的属性。
size_t
和 ptrdiff_t
在 64 位实现上保证为 64 位宽”等。保证被夸大了。 size_t
的范围主要由实现的内存容量决定。 “n 位实现”主要是整数的本机处理器宽度。当然,许多实现使用类似大小的内存和处理器总线宽度,但是存在内存不足的宽本机整数或内存很多的窄处理器,并且确实将这两个实现属性分开。
由于尚未有人提及它,因此 size_t
的主要语言意义是 sizeof
运算符返回该类型的值。同样,ptrdiff_t
的主要意义是从另一个指针中减去一个指针将产生该类型的值。接受它的库函数这样做是因为它允许此类函数在可能存在此类对象的系统上处理大小超过 UINT_MAX 的对象,而不会强制调用者浪费代码在较大类型的系统上传递大于“unsigned int”的值对于所有可能的对象就足够了。
malloc()
之类的事情。就个人而言,我希望看到采用 int
、long
和 long long
类型的参数的版本,其中一些实现促进更短的类型,而其他实现例如 lmalloc(long n) {return (n < 0 || n > 32767) ? 0 : imalloc(n);}
[在某些平台上,调用 {6 } 会比调用 lmalloc(123);
便宜,甚至在 size_t
为 16 位的平台上,想要分配以“long”值计算的大小的代码......
size_t
和 int
不可互换。例如,在 64 位 Linux 上,size_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 开始向上,请始终使用无符号类型以避免溢出导致您陷入负值情况。这一点非常重要,因为如果您的数组边界恰好小于循环的最大值,但您的循环最大值恰好大于您的类型的最大值,您将环绕负数并且您可能会遇到 segmentation fault ( SIGSEGV)。所以,一般来说,永远不要将 int 用于从 0 开始并向上的循环。使用无符号。
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
参数)。
为了让您大致了解堆栈大小:
嵌入式设备上的 4K
Win10 1M
在 Linux 上为 740 万
malloc
、memcpy
和 strlen
等许多 C 库函数将其参数和返回类型声明为 size_t
。
size_t
使程序员能够处理不同的类型,方法是添加/减去所需的元素数量,而不是使用以字节为单位的偏移量。
让我们通过检查它在 C 字符串和整数数组的指针算术运算中的用法来更深入地了解 size_t
可以为我们做什么:
下面是一个使用 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 位,但只有 32 bits on a Win64 system。因此,使用 t_size
将节省大量编码和潜在错误,尤其是在运行在不同架构上执行地址算术的 C 代码时。
所以这个故事的寓意是“使用 size_t
并让你的 C 编译器完成指针运算中容易出错的工作”。
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
是一个 unsigned
整数,其位大小足以容纳本机架构的指针。
所以:
sizeof(size_t) >= sizeof(void*)
size_t
。几个例子:x86 实模式下的 C 编译器可以有 32 位 FAR
或 HUGE
指针,但 size_t 仍然是 16 位。另一个例子:Watcom C 曾经有一个特殊的胖指针,用于 48 位宽的扩展内存,但 size_t
没有。在哈佛架构的嵌入式控制器上,您也没有相关性,因为两者都涉及不同的地址空间。
size_t
不定期副业成功案例分享
size_t
用于内存中的对象。 C 标准甚至没有定义stat()
或off_t
(这些是 POSIX 定义)或与磁盘或文件系统有关的任何内容 - 它在FILE
流处停止自身。就大小要求而言,虚拟内存管理与文件系统和文件管理完全不同,因此此处提及off_t
无关紧要。size_t
定义为sizeof
运算符的结果类型(关于<stddef.h>
的 7.17p2)。 6.5 节准确解释了 C 表达式的工作原理(6.5.3.4 用于sizeof
)。由于您不能将sizeof
应用于磁盘文件(主要是因为 C 甚至没有定义磁盘和文件的工作方式),因此没有混淆的余地。换句话说,责怪维基百科(以及这个引用维基百科而不是实际的 C 标准的答案)。