我对C中的size_t感到困惑,我知道它是由sizeof操作符返回的。但到底是什么?它是数据类型吗?
假设我有一个for循环:
1
| for(i = 0; i < some_size; i++) |
我应该使用int i;还是size_t i;?
- 如果这些是您唯一的选择,那么在签署some_size时使用int,在签署some_size时使用int,在签署size_t时使用size_t。
- @内特,这是不正确的。posix有一个ssize类型,但实际使用的正确类型是ptrdiff类型。
来自维基百科:
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是一种保证保存任何数组索引的类型。
- "采用或返回大小的库函数希望它们属于类型…大小"t",除了stat()对文件大小使用off_t
- @德雷蒙的评论反映了一个根本的困惑。size_t用于内存中的对象。C标准甚至没有定义stat()或off_t(这些是posix定义)或与磁盘或文件系统有关的任何内容—它在FILE流中停止。就大小要求而言,虚拟内存管理与文件系统和文件管理完全不同,因此这里提到的off_t与此无关。
- @JW013:我很难称之为根本性的困惑,但你提出了一个有趣的观点。尽管如此,引用的文本并没有说"内存中对象的大小","偏移量"对于一个大小类型来说,无论它存储在何处,都不是一个好名称。
- @德雷蒙说得对。这个答案引用了维基百科,在我看来,它没有最好的解释。C标准本身更为明确:它将size_t定义为sizeof运算符(关于的7.17p2)的结果类型。第6.5节详细解释了C表达式的工作原理(6.5.3.4适用于sizeof)。由于您不能将sizeof应用于磁盘文件(主要是因为C甚至没有定义磁盘和文件的工作方式),因此没有混淆的空间。换句话说,指责维基百科(这个答案引用了维基百科,而不是实际的C标准)。
- @德拉蒙-我也同意"基本困惑"评估。如果你还没有阅读C/C++标准,你可能会认为"对象"是指"面向对象的编程",而不是。阅读C标准,它没有那些OOP对象,但有对象,并找出。答案可能会让你吃惊!
- 同样值得注意的是,对于某些现有的无符号整数类型,size_t几乎是一个typedef。例如,size_t通常与unsigned long的类型相同(typedef为现有类型创建别名,而不是新类型)。但您不应该假定size_t与任何特定类型相同,因为它可能因系统而异。
- 所以16位意味着它只是一个无符号的空头?
- 好吧,sizeof告诉我它是8字节,所以肯定大于16位,至少对我来说。
size_t是无符号类型。因此,它不能表示任何负值(<0)。你在数东西的时候会用到它,并且要确保它不会是负数。例如,strlen()返回size_t,因为字符串的长度必须至少为0。
在您的示例中,如果循环索引总是大于0,那么使用size_t或任何其他无符号数据类型可能是有意义的。
当使用size_t对象时,必须确保在所有使用它的上下文中(包括算术),都需要非负值。例如,假设您有:
你想找出str2和str1的长度之差。你不能这样做:
1
| int diff = s2 - s1; /* bad */ |
号
这是因为分配给diff的值总是一个正数,即使在s2 < s1时也是如此,因为计算是用无符号类型完成的。在这种情况下,根据您的用例是什么,您最好使用int(或long long用于s1和s2。
c/posix中有一些函数可以/应该使用size_t,但由于历史原因,这些函数不能使用。例如,fgets的第二个参数在理想情况下应该是size_t,但是是int。
- @阿洛克:两个问题:1)size_t的大小是多少?2)为什么我更喜欢size_t而不是unsigned int?
- size_t并不能保证与unsigned int是一样的(你似乎暗示它们是一样的)。
- @拉撒:以东十一〔0〕号是以东十一〔20〕号。C标准保证SIZE_MAX至少为65535。size_t是sizeof运算符返回的类型,用于标准库(例如strlen返回size_t中)。正如布伦丹所说,size_t不必与unsigned int相同。
- @阿洛克,@brendan long:当你说size_t与unsigned int不一样时,我猜你的意思是,虽然它实际上是unsigned,但不能保证它是int。是真的吗?
- @是的,size_t保证是无符号类型。
- @阿洛克:谢谢!
- s2-s1并不总是一个正数。
- @杰西蒙斯:这就是我的观点。如果s1和s2都是无符号类型(例如size_t),那么即使在s1 < s2时,结果s1-s2也不能为负。这是因为无符号类型没有负值。当您将该差异分配给int时,结果是由实现定义的。这就是为什么说在我的例子中,我们不想考虑无符号类型的差异,因为我们必须能够方便地得到可能的负差异。
- "所以,它可以表示非负值"你的意思是不是不能?
- @不,我的意思是无符号类型只能表示非负值。我可能应该说"它不能代表负值"。
- 这是因为s2-s1并不总是正数(例如s2=5;s1=20;s2-s1=-15)
- @德里克约翰逊,我澄清了我回答的那部分。
- 请记住,在64位Linux上,in t总是32位的,但大小为64位。所以大小t和int不能互换。
- 如果出于任何原因需要对大小进行签名,那么Linux上有ssize-t类型。
- 这不是二者的互补作用!size_t s2 = 5, s1 = 20; int diff = s2 - s1; printf("%d: %x
", diff, diff < 0);这个打印的正是你所期望的:"-15: 1"。
- @Jasonoster,在C标准中不要求二者的补充。如果s2 - s1的值溢出int,则行为未定义。
- @对于C规范,你是正确的。事实上,一个人的补充仅限于少量的历史主机和一些特殊的应用程序,如ADC。事实上,二者的互补是如此普遍,以至于到目前为止它已经成为一个事实上的标准。整数溢出可能很危险,但范围超出了混合符号。
size_t是一种可以保存任何数组索引的类型。
根据实现的不同,它可以是以下任何一种:
埃多克斯1〔32〕
埃多克斯1〔33〕
埃多克斯1〔34〕
埃多克斯1〔35〕
埃多克斯1〔36〕
以下是我的机器的stddef.h中定义size_t的方法:
1
| typedef unsigned long size_t; |
。
- 当然,typedef unsigned long size_t依赖于编译器。或者你是说总是这样?
- @Chux:事实上,仅仅因为一个实现将其定义为这样,并不意味着所有的事情都要做。例如:64位窗口。unsigned long为32位,size_t为64位。
- 尺寸的目的到底是什么?当我可以为自己创建一个变量时,比如:"int mysize"或"long mysize"或"unsigned long mysize"。为什么有人要为我创建这个变量?
- @midkin size_t不是变量。它是一种类型,当您想要表示内存中对象的大小时,可以使用它。
- 在32位机器上,size_t总是32位,同样是64位,这是真的吗?
- "根据1999年的ISO C标准(C99),大小T是至少16位的无符号整数类型(见第7.17和7.18.3节)。"因此它不能是unsigned char?
- @Jameshfisher我不确定16位限制是否正确。uint_least16_t至少是16位。关于size_t,标准中说"size of运算符的结果的无符号整数类型"和"sizeof运算符生成其操作数的大小(以字节为单位)。
- @jameshfisher谁说unsigned char不能是16位?!
- @Anttihaapala这是一个很好的观点;显然,保证unsigned char至少包含0-255的值。
如果你是经验型的,
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外观
- 我有同样的环境,但是,我已经测试了32位,通过gcc的"-m32"选项,结果是:"typedef unsigned int size_t"。感谢分享这个很棒的命令@ciro,它帮助了我很多!-)
- 这件事本身并不令人困惑。正是这种迷惑的思想试图提出许多问题,并给出许多答案。令我惊讶的是,这个答案和阿琼·斯雷德哈兰的答案仍然没有阻止人们提问和回答。
- 很好的答案,因为它实际上告诉你什么是size_t,至少在流行的Linux发行版上是这样。
H型的手册页上写着:
size_t shall be an unsigned integer type
号
由于还没有人提到它,size_t的主要语言意义是sizeof运算符返回该类型的值。同样,ptrdiff_t的主要意义在于,从另一个指针中减去一个指针将产生该类型的值。接受它的库函数之所以这样做,是因为它允许此类函数在可能存在此类对象的系统上处理大小超过uint_max的对象,而不强制调用方在系统上浪费代码,在系统上传递的值大于"unsigned int",在系统上,较大的类型足以容纳所有可能的对象。
- 我的问题一直是:如果sizeof不存在,是否需要尺寸?
- @迪安普:也许不会,不过接下来会有一个问题,即什么样的论证类型应该用于像malloc()这样的事情。就我个人而言,我希望看到一些版本采用int、long和long long类型的论点,其中一些实现促进较短的类型,而另一些实现(例如lmalloc(long n) {return (n < 0 || n > 32767) ? 0 : imalloc(n);}在某些平台上实现,调用imalloc(123)比调用lmalloc(123);便宜,甚至在一个平台上调用imalloc(123)。size_t是16位的代码,它希望分配以"long"值计算的大小…
- …应该能够依赖分配失败,如果值大于分配程序可以处理的值。
size_t和int不能互换。例如,在64位Linux上,size_t的大小为64位(即sizeof(void*)),但int的大小为32位。
还要注意,size_t是无符号的。如果您需要签名的版本,那么在某些平台上有ssize_t,它与您的示例更相关。
作为一般规则,我建议在大多数一般情况下使用int,只有在有特殊需要时(例如,mmap())才使用size_t/ssize_t。
要了解为什么size_t需要存在以及我们是如何到达这里的:
从实用角度来说,size_t和ptrdiff_t在64位实现上保证64位宽,在32位实现上保证32位宽,等等。在不破坏遗留代码的情况下,它们无法强制任何现有类型在每个编译器上表示这一点。
size_t或ptrdiff_t不一定与intptr_t或uintptr_t相同。它们在某些体系结构上是不同的,这些体系结构在80年代末将size_t和ptrdiff_t添加到标准中时仍在使用,而在c99添加了许多新类型但尚未消失时(如16位窗口),它们就变得过时了。16位保护模式下的x86具有分段内存,其中可能最大的数组或结构的大小只能为65536字节,但far指针需要32位宽,比寄存器宽。在这种情况下,intptr_t的宽度应该是32位,但size_t和ptrdiff_t的宽度可以是16位,并适合于寄存器。谁知道将来会写什么样的操作系统呢?理论上,i386体系结构提供了一个32位分段模型,其中包含48位指针,这是任何操作系统都没有实际使用过的。
内存偏移量的类型不能是long,因为太多的遗留代码假定long正好32位宽。这种假设甚至内置于Unix和Windows API中。不幸的是,许多其他遗留代码还假定long足够宽,可以容纳指针、文件偏移量、1970年以来经过的秒数等。现在,POSIX提供了一种标准化的方法来强制后一种假设为真,而不是前一种假设,但两者都不是可移植的假设。
它不能是int,因为90年代只有极少数的编译器使int64位宽。然后,通过保持long32位的宽度,他们真的变得很奇怪。该标准的下一次修订宣布,int比long宽是非法的,但在大多数64位系统上,int仍然是32位宽。
它不能是long long int,因为它被创建为至少64位宽,即使是在32位系统上。
因此,需要一种新的类型。即使不是,所有其他类型都意味着数组或对象中的偏移量以外的其他值。如果说从32到64位迁移的失败中得到了一个教训,那就是具体说明一个类型需要什么属性,而不是在不同的程序中使用一个表示不同事物的属性。
通常,如果从0开始向上,则始终使用无符号类型以避免溢出导致负值情况。这一点非常重要,因为如果数组边界恰好小于循环的最大值,但循环的最大值恰好大于类型的最大值,则会将负值环绕起来,并且可能会遇到分段错误(sigsegv)。所以,一般来说,对于从0开始向上的循环,永远不要使用int。使用无符号。
- 我不能接受你的论点。你说溢出错误会悄悄地导致访问数组中的有效数据更好吗?
- @MAF软件正确。如果错误没有被发现,它会比程序崩溃更糟糕。为什么这个答案得到了赞成票?
- 如果它访问数组中的有效数据,那么它不是错误,因为无符号类型不会在有符号类型将要达到的极限时溢出。这是什么逻辑的家伙?假设出于某种原因,您使用char迭代256个元素数组…signed将在127溢出,128元素将sigsegv溢出,但如果使用unsigned,那么它将按预期遍历整个数组。同样,当你使用一个int时,你的数组实际上不会超过20亿个元素,所以不管怎样都不重要…
- 我无法想象在任何情况下整数溢出都不是bug,不管它是围绕正的还是负的。仅仅因为你没有得到一个segfault并不意味着你看到了正确的行为!不管偏移量是正的还是负的,你都可以经历一个分割错误;这都取决于你的内存布局。@purpleice,我不认为你说的是和这个答案一样的话;你的论点是你应该选择一个足够大的数据类型来保存你想要放入的最大值,这是很简单的常识。
- 也就是说,我更喜欢在语义上对循环索引使用无符号类型;如果您的变量永远不会是负数,那么您也可以在您选择的类型中指出这一点。它还可以让编译器发现一个错误,该错误的值最终为负数,但gcc至少在发现这个特定错误方面非常糟糕(有一次我将无符号初始化为-1,但没有收到警告)。同样,大小在语义上适合数组索引。
大小为无符号整数数据类型。在使用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*) |
- 不是真的。指针大小可以大于size_t。例如:x86实模式下的C编译器可以有32位FAR或HUGE指针,但大小仍然是16位。另一个例子:watcom c以前有一个特殊的胖指针,用于48位宽的扩展内存,但是size_t没有。在具有哈佛架构的嵌入式控制器上,您也没有相关性,因为两者都涉及不同的地址空间。
- 在stackoverflow.com/questions/1572099/&hellip;上有更多的例子,如带有128位指针和32位size_t的as/400。
- 这显然是错误的。不过,我们还是把它放在这儿吧