关于int:C中的size_t是什么?

What is size_t in C?

我对C中的size_t感到困惑,我知道它是由sizeof操作符返回的。但到底是什么?它是数据类型吗?

假设我有一个for循环:

1
for(i = 0; i < some_size; i++)

我应该使用int i;还是size_t i;


来自维基百科:

According to the 1999 ISO C standard
(C99), size_t is an unsigned integer
type of at least 16 bit (see sections
7.17 and 7.18.3).

size_tis an unsigned data type
defined by several C/C++ standards,
e.g. the C99 ISO/IEC 9899 standard,
that is defined in stddef.h.1 It can
be further imported by inclusion of
stdlib.h as this file internally sub
includes stddef.h.

This type is used to represent the
size of an object. Library functions
that take or return sizes expect them
to be of type or have the return type
of size_t. Further, the most
frequently used compiler-based
operator sizeof should evaluate to a
constant value that is compatible with
size_t.

也就是说,size_t是一种保证保存任何数组索引的类型。


size_t是无符号类型。因此,它不能表示任何负值(<0)。你在数东西的时候会用到它,并且要确保它不会是负数。例如,strlen()返回size_t,因为字符串的长度必须至少为0。

在您的示例中,如果循环索引总是大于0,那么使用size_t或任何其他无符号数据类型可能是有意义的。

当使用size_t对象时,必须确保在所有使用它的上下文中(包括算术),都需要非负值。例如,假设您有:

1
2
size_t s1 = strlen(str1);
size_t s2 = strlen(str2);

你想找出str2str1的长度之差。你不能这样做:

1
int diff = s2 - s1; /* bad */

这是因为分配给diff的值总是一个正数,即使在s2 < s1时也是如此,因为计算是用无符号类型完成的。在这种情况下,根据您的用例是什么,您最好使用int(或long long用于s1s2

c/posix中有一些函数可以/应该使用size_t,但由于历史原因,这些函数不能使用。例如,fgets的第二个参数在理想情况下应该是size_t,但是是int


size_t是一种可以保存任何数组索引的类型。

根据实现的不同,它可以是以下任何一种:

埃多克斯1〔32〕

埃多克斯1〔33〕

埃多克斯1〔34〕

埃多克斯1〔35〕

埃多克斯1〔36〕

以下是我的机器的stddef.h中定义size_t的方法:

1
typedef unsigned long size_t;


如果你是经验型的,

1
echo | gcc -E -xc -include 'stddef.h' - | grep size_t

Ubuntu 14.04 64位GCC 4.8的输出:

1
typedef long unsigned int size_t;

注意,stddef.h由GCC提供,而非GCC 4.2中src/gcc/ginclude/stddef.h项下的GLIBC。

有趣的C99外观

  • mallocsize_t为参数,确定可分配的最大大小。

    由于它也是由sizeof返回的,所以我认为它限制了任何数组的最大大小。

    另请参见:C中数组的最大大小是多少?


H型的手册页上写着:

size_t shall be an unsigned integer type


由于还没有人提到它,size_t的主要语言意义是sizeof运算符返回该类型的值。同样,ptrdiff_t的主要意义在于,从另一个指针中减去一个指针将产生该类型的值。接受它的库函数之所以这样做,是因为它允许此类函数在可能存在此类对象的系统上处理大小超过uint_max的对象,而不强制调用方在系统上浪费代码,在系统上传递的值大于"unsigned int",在系统上,较大的类型足以容纳所有可能的对象。


size_tint不能互换。例如,在64位Linux上,size_t的大小为64位(即sizeof(void*)),但int的大小为32位。

还要注意,size_t是无符号的。如果您需要签名的版本,那么在某些平台上有ssize_t,它与您的示例更相关。

作为一般规则,我建议在大多数一般情况下使用int,只有在有特殊需要时(例如,mmap())才使用size_t/ssize_t


要了解为什么size_t需要存在以及我们是如何到达这里的:

从实用角度来说,size_tptrdiff_t在64位实现上保证64位宽,在32位实现上保证32位宽,等等。在不破坏遗留代码的情况下,它们无法强制任何现有类型在每个编译器上表示这一点。

size_tptrdiff_t不一定与intptr_tuintptr_t相同。它们在某些体系结构上是不同的,这些体系结构在80年代末将size_tptrdiff_t添加到标准中时仍在使用,而在c99添加了许多新类型但尚未消失时(如16位窗口),它们就变得过时了。16位保护模式下的x86具有分段内存,其中可能最大的数组或结构的大小只能为65536字节,但far指针需要32位宽,比寄存器宽。在这种情况下,intptr_t的宽度应该是32位,但size_tptrdiff_t的宽度可以是16位,并适合于寄存器。谁知道将来会写什么样的操作系统呢?理论上,i386体系结构提供了一个32位分段模型,其中包含48位指针,这是任何操作系统都没有实际使用过的。

内存偏移量的类型不能是long,因为太多的遗留代码假定long正好32位宽。这种假设甚至内置于Unix和Windows API中。不幸的是,许多其他遗留代码还假定long足够宽,可以容纳指针、文件偏移量、1970年以来经过的秒数等。现在,POSIX提供了一种标准化的方法来强制后一种假设为真,而不是前一种假设,但两者都不是可移植的假设。

它不能是int,因为90年代只有极少数的编译器使int64位宽。然后,通过保持long32位的宽度,他们真的变得很奇怪。该标准的下一次修订宣布,intlong宽是非法的,但在大多数64位系统上,int仍然是32位宽。

它不能是long long int,因为它被创建为至少64位宽,即使是在32位系统上。

因此,需要一种新的类型。即使不是,所有其他类型都意味着数组或对象中的偏移量以外的其他值。如果说从32到64位迁移的失败中得到了一个教训,那就是具体说明一个类型需要什么属性,而不是在不同的程序中使用一个表示不同事物的属性。


通常,如果从0开始向上,则始终使用无符号类型以避免溢出导致负值情况。这一点非常重要,因为如果数组边界恰好小于循环的最大值,但循环的最大值恰好大于类型的最大值,则会将负值环绕起来,并且可能会遇到分段错误(sigsegv)。所以,一般来说,对于从0开始向上的循环,永远不要使用int。使用无符号。


大小为无符号整数数据类型。在使用GNU C库的系统上,这将是无符号int或无符号long int。大小通常用于数组索引和循环计数。


当循环变量通常大于或等于0时,可以将大小t或任何无符号类型视为循环变量。

当我们使用大小对象时,我们必须确保在所有使用它的上下文中,包括算术,我们只需要非负值。例如,下面的程序肯定会产生意想不到的结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 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


据我所知,size_t是一个unsigned整数,其位大小足以容纳本机架构的指针。

所以:

1
sizeof(size_t) >= sizeof(void*)