我对C语言中的size_t
感到困惑。我知道它是由sizeof
运算符返回的。但它究竟是什么?它是一种数据类型吗?
假设我有一个for
循环:
for(i = 0; i < some_size; i++)
我应该使用int i;
还是size_t i;
?
size_t
用于内存中的对象。C标准甚至没有定义 stat()
或 off_t
(它们是POSIX定义),也没有与磁盘或文件系统有关的任何内容 - 它只停留在 FILE
流上。对于大小要求而言,虚拟内存管理与文件系统和文件管理完全不同,因此在这里提到 off_t
是不相关的。 - jw013size_t
为sizeof
操作符的结果类型(有关<stddef.h>
的7.17p2)。第6.5节详细解释了C表达式的工作方式(sizeof
的6.5.3.4)。由于你无法对磁盘文件应用sizeof
(主要是因为C甚至未定义磁盘和文件的工作方式),因此不会产生混淆。换句话说,应该责备维基百科(以及这个回答引用了维基百科而非实际的C标准)。 - jw013size_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
的大小是多少?2)为什么我应该优先选择size_t
而不是像unsigned int
这样的东西? 回答:size_t
的大小是根据系统架构来确定的,通常在32位系统上为4个字节,在64位系统上为8个字节。size_t
,因为它是专门用于表示对象大小或容器元素数量的类型,并且可以确保足够大以适应任何平台。相比之下,unsigned int
具有固定的大小(通常为4个字节),可能无法足够大,而导致数据截断或溢出。size_t
的大小为 sizeof(size_t)
。C 标准保证 SIZE_MAX
至少为 65535。size_t
是由 sizeof
运算符返回的类型,并且在标准库中使用(例如 strlen
返回 size_t
)。正如 Brendan 所说,size_t
不一定与 unsigned int
相同。 - Alok Singhalsize_t
被保证是无符号类型。 - Alok Singhals2-s1
的值溢出了int
类型,行为将是未定义的。 - Alok Singhalsize_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位。 - Tim Čassize_t
是否总是32位,64位机器同理? - John Wuuint_least16_t
表示至少为16位。对于size_t
,标准规定“无符号整型是sizeof运算符的结果”,并且“sizeof运算符返回其操作数的大小(以字节为单位)”。 - Arjun Sreedharan如果您是实证主义者,
echo | gcc -E -xc -include 'stddef.h' - | grep size_t
Ubuntu 14.04 64位GCC 4.8的输出:
typedef long unsigned int size_t;
请注意,在GCC 4.2中,stddef.h
由GCC提供而不是glibc,位于src/gcc/ginclude/stddef.h
中。
C99的一些有趣表现
malloc
以size_t
作为参数,因此它确定可以分配的最大大小。
由于它也是sizeof
返回的值,我认为它限制了任何数组的最大大小。
另请参见:C中数组的最大大小是多少?
echo
管道来获取一个空的源文件。 - Dannysize_t
并介绍我们是如何到达这里的:size_t
和 ptrdiff_t
在 64 位实现中保证为 64 位宽,在 32 位实现中为 32 位宽等等。不可能强制任何现有类型在每个编译器上都具有这种意义,因为会破坏遗留代码。
size_t
或 ptrdiff_t
不一定与 intptr_t
或 uintptr_t
相同。当在1980年代后期添加 size_t
和 ptrdiff_t
到标准中时,它们在某些仍在使用的体系结构上是不同的,并且在 C99 中添加了许多新类型但尚未消失(例如16位Windows)。在16位保护模式下的x86具有分段内存,最大可能的数组或结构的大小只能为65,536字节,但一个 far
指针需要32位宽,比寄存器宽。在这些体系结构上,intptr_t
可以是32位宽,但 size_t
和 ptrdiff_t
可以是16位宽并适合于寄存器中。而且谁知道将来会编写什么样的操作系统呢?理论上,i386体系结构提供了一个32位分段模型和48位指针,但没有任何操作系统实际使用过。 long 的精度为32位,因此内存偏移量的类型不能为 long 。 这个假设甚至内置于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 位实现" 主要是整数的本机处理器宽度。当然,许多实现使用类似大小的内存和处理器总线宽度,但是具有稀少内存或窄处理器的宽本机整数或具有大量内存的窄处理器确实存在,并且会使这两个实现属性分离。 - chux - Reinstate Monica因为还没有人提到,所以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);}
[在某些平台上,调用imalloc(123)
比调用lmalloc(123)
更便宜,即使在size_t
为16位的平台上,代码也可以分配由long
值计算出的大小... - supercatsize_t
和int
不能互换使用。例如,在64位Linux上,size_t
的大小为64位(即sizeof(void*)
),而int
的大小为32位。
另外,请注意size_t
是无符号的。如果您需要有符号版本,则某些平台上有ssize_t
,它可能更适合您的示例。
作为一般规则,我建议在通用情况下使用int
,只有在计算内存偏移量时(例如使用mmap()
)才使用size_t
/ssize_t
。
size_t
是一种无符号整数数据类型,只能赋值为0和大于0的整数值。它用于测量任何对象的大小(以字节为单位),并由 sizeof
运算符返回。
const
是 size_t
的语法表示,但如果没有 const
,程序也可以运行。
const size_t number;
size_t
通常用于数组索引和循环计数。如果编译器是32位,则它将使用unsigned int
,如果编译器是64位,则还将使用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);
}
输出: 大小 = 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);
}
输出: 大小 = 800
size_t
是一个类型定义(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
使程序员能够使用添加/减去所需元素数而不是使用字节偏移量来处理不同类型。
通过检查C字符串和整数数组的指针算术运算中使用size_t
的用法,让我们更深入地了解 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
在传递int数组时的方法:
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位)。
如果我们创建一个长整型数组,我们会发现在linux64操作系统上,long占用64位,但在Win64系统上只占用32位。因此,在不同架构的C代码上运行时,使用可以节省大量编码和潜在的错误,特别是执行地址算术运算时更是如此。
因此,这个故事的寓意是“使用size_t
并让你的C编译器处理容易出错的指针算术工作。”
some_size
是有符号的,使用int
,如果它是无符号的,使用size_t
。 - Nate