C中的size_t是什么?

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

假设我有一个for循环:

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

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

941637 次浏览

类型h的手册说:

size_t应为无符号整数类型

来自wikipedia

根据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()返回size_t,因为字符串的长度必须至少为0。

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

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

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

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

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

这是因为分配给diff的值总是正数,即使是s2 < s1,因为计算是使用无符号类型完成的。在这种情况下,根据您的用例,您可能最好对s1s2使用int(或long long)。

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

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

所以:

sizeof(size_t) >= sizeof(void*)

size_tint不能互换。例如在64位Linuxsize_t是64位大小(即sizeof(void*)),但int是32位。

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

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

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

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

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

unsigned char

unsigned short

unsigned int

unsigned long

unsigned long long

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

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提供,而不是GCC 4.2中src/gcc/ginclude/stddef.h下的glibc。

有趣的C99外观

  • mallocsize_t作为参数,因此它确定可以分配的最大大小。

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

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

由于还没有人提到它,size_t的主要语言意义在于sizeof运算符返回该类型的值。类似地,ptrdiff_t的主要意义在于从另一个指针中减去一个指针将产生该类型的值。接受它的库函数这样做是因为它将允许此类函数在可能存在此类对象的系统上使用大小超过UINT_MAX的对象,而不会迫使调用者浪费代码在较大类型足以满足所有可能对象的系统上传递大于“无符号int”的值。

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

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 finefor (size_t n = 0; n < N; ++n)a[n] = n;
// But reverse cycles are tricky for unsigned// types as can lead to infinite loopfor (size_t n = N-1; n >= 0; --n)printf("%d ", a[n]);}
OutputInfinite loop and then segmentation fault

进入为什么size_t需要存在以及我们如何来到这里:

实际上,size_tptrdiff_t在64位实现上保证为64位宽,在32位实现上保证为32位宽,依此类推。它们不能在不破坏遗留代码的情况下强制任何现有类型在每个编译器上表示这一点。

size_tptrdiff_t不一定与intptr_tuintptr_t相同。它们在某些架构上是不同的,当size_tptrdiff_t在20世纪80年代末被添加到标准中时,这些架构仍在使用,当ptrdiff_t0添加了许多新类型但尚未消失时(例如16位Windows),它们就过时了。16位保护模式下的x86具有分段内存,其中最大的数组或结构的大小只能是65,536字节,但far指针需要32位宽,比寄存器宽。在这些上,intptr_t将是32位宽,但size_tptrdiff_t可能是16位宽并适合寄存器。谁知道将来会编写什么样的操作系统?理论上,i386架构提供了一个32位分段模型,具有48位指针,没有操作系统实际使用过。

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

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

它不可能是long long int,无论如何它是后来添加的,因为即使在32位系统上,它也被创建为至少64位宽。

因此,需要一种新类型。即使不是,所有其他类型都意味着数组或对象中的偏移量以外的东西。如果说32位到64位迁移的惨败有一个教训,那就是具体说明一个类型需要拥有哪些属性,而不是使用一个在不同程序中意味着不同东西的类型。

size_t是一种无符号整数数据类型,只能分配0和大于0的整数值。它测量任何对象大小的字节,并由sizeof运算符返回。

constsize_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

这是一个特定于平台的typedef。例如,在特定的机器上,它可能是unsigned intunsigned 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参数)。

为了让您了解近似堆栈大小:

  • 嵌入式设备上的4K
  • 1MWin10
  • 7.4MLinux

许多C库函数(如mallocmemcpystrlen)将它们的参数声明为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"  // <= orig0x7ff626939006 "3"    // <= rev - 1 of 30x7ff626939005 "23"   // <= rev - 2 of 30x7ff626939004 "123"  // <= rev - 3 of 30x7ff6aade9003 ""     // <= 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  // <= orig0x617d3ffb4c 3  // <= rev - 1 of 30x617d3ffb48 2  // <= rev - 2 of 30x617d3ffb44 1  // <= rev - 3 of 3

上面,我们看到一个int需要4个字节(因为每个字节有8位,一个int占用32位)。

如果我们要创建一个long数组,我们会发现long在linux64操作系统上需要64位,但只需要Win64系统上的32位。因此,使用t_size将节省大量编码和潜在的错误,尤其是在运行在不同架构上执行地址算术的C代码时。

所以这个故事的寓意是“使用size_t,让你的C编译器做指针算术的容易出错的工作。”