无符号整数 vs. size_t

579

我注意到现代 C 和 C++ 代码似乎在几乎每个地方都使用 size_t,而不是 int/unsigned int,从 C 字符串函数的参数到 STL。我很好奇这样做的原因和带来的好处。

8个回答

464

size_t类型是无符号整数类型,它是sizeof运算符(和offsetof运算符)的结果,因此保证足够大,可以容纳系统能处理的最大对象的大小(例如8Gb的静态数组)。

size_t类型可能比unsigned int大、等于或小于,您的编译器可能会针对其进行优化假设。

您可以在C99标准的第7.17节中找到更精确的信息,其中一份草案可在Internet上以pdf格式获得,或者在C11标准的第7.19节中,也可作为pdf草案获取。


58
不。考虑x86-16采用大型(而非巨型)内存模型:指针远离(32位),但单个对象的大小限制为64k(因此size_t可以是16位)。 - dan04
11
“size of the biggest object” 并不是用词不当,而是绝对正确的。一个对象的大小可以比地址空间更受限制。 - gnasher729
3
我希望编译器知道 size_t 可以表示的确切值范围!如果它不知道,那么谁知道呢?"your compiler might make assumption about it"。 - Marc van Leeuwen
4
我认为重点更多是编译器可能会利用那些知识来处理某些事情。 - user1084944
8
希望这种越来越流行的类型不需要包含头文件。 - user2023370
显示剩余6条评论

114

9
另一篇很好的文章解释了size_t和ptrdiff_t:http://www.viva64.com/en/a/0050/ - Ihor Kaharlichenko

102

size_t是永远不会为负数的类型,它最大程度地提高了性能,因为它被typedef为无符号整数类型,足以表示目标平台上最大可能对象的大小,但不会过大。

尺寸永远不应该为负数,而且size_t是无符号类型。此外,由于size_t是无符号的,我们可以存储大约两倍于相应有符号类型的数字,因为我们可以使用符号位来表示大小,就像无符号整数中的其他位一样。当我们获得一个额外的比特时,我们将所能表示的数字范围乘以大约二。

那么你会问,为什么不使用unsigned int?可能无法容纳足够大的数字。在实现中,如果unsigned int是32位的,则它可以表示的最大数字为4294967295。一些处理器(例如IP16L32)可以复制大于4294967295字节的对象。

那么你会问,为什么不使用unsigned long int?它会对某些平台产生性能损失。标准C要求long至少占用32位。IP16L32平台将每个32位长作为一对16位字实现。几乎所有的32位运算符在这些平台上都需要两个或更多指令,因为它们使用两个16位块中的32位。例如,移动32位长通常需要两个机器指令——分别移动每个16位块。

使用 size_t 可以避免这种性能损失。根据这篇绝妙的文章,“类型 size_t 是一个 typedef,它是某个无符号整数类型的别名,通常是 unsigned intunsigned long ,但也可能是 unsigned long long。每个标准的 C 实现都应该选择足够大(但不过大)的无符号整数来表示目标平台上最大可能的对象大小。”


1
很抱歉这么晚才评论,但我必须确认一下无符号整数可以容纳的最大数字——也许我误解了您的术语,但我认为无符号整数可以容纳的最大数字是4294967295,而无符号短整型的最大值是65356。 - Mitch
1
如果你的无符号整型占据32位,那么是的,它可以容纳的最大数值是2^32-1,即4294967295(0xffffffff)。你还有其他问题吗? - Rose Perrone
4
@Mitch:在不同的系统中,可以表示为“unsigned int”的最大值可以并且确实有所不同。它必须至少为“65536”,但通常为“4294967295”,在某些系统上可能是“18446744073709551615”(2 ** 64-1)。 - Keith Thompson
1
一个16位无符号整数可以包含的最大值是65535,而不是65536。这是一个微小但重要的区别,因为在16位无符号整数中,65536与0相同。 - Sie Raybould
1
@gnasher729:你确定C++标准吗?我搜索了一段时间,我的印象是他们只是删除了关于整数范围的所有绝对保证(不包括“unsigned char”)。标准似乎没有包含字符串“65535”或“65536”,而“+32767”仅在注释中出现(1.9:9),作为int可表示的最大整数的可能值;甚至没有保证INT_MAX不能比它更小! - Marc van Leeuwen
显示剩余7条评论

52

size_t类型是sizeof操作符返回的类型。它是一种无符号整数,能够表示主机机器支持的任何内存范围的字节数量。它通常与ptrdiff_t相关,因为ptrdiff_t是有符号整数值,使得sizeof(ptrdiff_t)和sizeof(size_t)相等。

在编写C代码时,处理内存范围时应始终使用size_t。

另一方面,int类型基本上被定义为主机机器可以使用最有效地执行整数算术的(有符号)整数值的大小。例如,在许多较旧的PC型计算机上,sizeof(size_t)的值将为4(字节),但sizeof(int)的值将为2(字节)。 16位算术比32位算术更快,尽管CPU可以处理高达4 GiB的(逻辑)内存空间。

仅在关心效率时使用int类型,因为其实际精度强烈依赖于编译器选项和机器架构。特别是C标准规定以下不变式:sizeof(char) <= sizeof(short) <= sizeof(int) <= sizeof(long),对程序员每个原始类型可用的精度表示没有其他限制。

注意:这与Java不同(Java实际上指定了每个类型'char','byte','short','int'和'long'的位精度)。


int 的事实标准定义是在 16 位机器上为 16 位,在任何更大的机器上为 32 位。已经编写了太多假定 int 宽度为 32 位的代码,现在改变这一点会导致问题,因此人们应该始终使用 size_t 或 {,u}int{8,16,32,64}_t 如果他们想要特定的东西 - 作为预防措施,人们应该始终使用这些,而不是整数类型。 - Clearer
3
size_t是一种无符号整数,能够表示主机支持的任何内存范围的字节数大小。” --> 不对。size_t能够表示任何单个对象(例如数字、数组、结构体)的大小。整个内存范围可能超过size_t的表示范围。 - chux - Reinstate Monica
编写 C 代码时,处理内存范围时应始终使用 size_t。这意味着每个数组的每个索引都应该是 size_t 吗?我希望你不是这个意思。大多数情况下,我们不需要处理地址空间和可移植性等问题的数组。在这些情况下,您可以使用 size_t。在其他情况下,您可以从(有符号)整数中取出索引。因为无符号整数的未预期下溢行为所导致的混淆比可能在其他情况下出现的可移植性问题更常见且更糟糕。 - johannes_lalala
@johannes_lalala,我希望你不是这个意思。没有其他类型可以保证足够大以容纳最大的有效数组索引。当涉及到溢出或下溢时,有符号整数会导致UB,而无符号整数的溢出和下溢不会导致UB,但是是明确定义的。对于所有非负数组索引,应该使用size_t - 12431234123412341234123
是的,我做到了。你几乎从未需要大索引,我在20年中从未需要过。如果你认为可能会遇到大索引,请使用size_t。在所有其他情况下,请不要使用。考虑这个常见的范围验证:index = x - y.. later: if (index < 0) -> fail, otherwise z = arr[index].. 如果您在此处使用无符号整数会发生什么。顺便说一下,这也是C++委员会对这个主题的官方立场。 - johannes_lalala

24

类型size_t必须足够大,以便存储任何可能对象的大小。无符号整型不必满足该条件。

例如,在64位系统中,int和unsigned int可能是32位宽度,但size_t必须足够大,以存储大于4G的数字。


38
“object”是该标准所使用的术语。 - R.. GitHub STOP HELPING ICE
2
我认为 size_t 只有在编译器能够接受类型 X 的情况下才需要那么大,使得 sizeof(X) 会产生一个大于 4G 的值。大多数编译器都会拒绝例如 typedef unsigned char foo[1000000000000LL][1000000000000LL],即使 foo[65536][65536] 超过了文档中定义的实现限制,也可以合理地被拒绝。 - supercat
1
@MattJoiner:措辞很好。 “Object”一点也不含糊,而是被定义为“存储区域”。 - Lightness Races in Orbit

4
这里摘自glibc手册0.02,也许在研究该主题时有参考价值:
size_t类型在GCC 2.4之前的版本中存在潜在问题。ANSI C要求size_t始终是无符号类型。为了与现有系统的头文件兼容,GCC在stddef.h中将size_t定义为系统sys/types.h定义的任何类型。大多数在“sys/types.h”中定义size_t的Unix系统将其定义为带符号类型。库中的某些代码依赖于size_t是无符号类型,并且如果它是带符号的,则不会正常工作。
期望size_t为无符号的GNU C库代码是正确的。将size_t定义为带符号类型是错误的。我们计划在版本2.4中,GCC将始终将size_t定义为无符号类型,并且fixincludes脚本将调整系统的sys/types.h以避免冲突。
同时,我们通过明确告诉GCC在编译GNU C库时使用无符号类型来解决这个问题。configure将自动检测GCC用于size_t的类型并在必要时进行覆盖。

2
如果我的编译器设置为32位,那么size_t只是一个unsigned int的typedef。如果我的编译器设置为64位,那么size_t只是一个unsigned long long的typedef。请注意,这里的typedef是一种定义新类型的方式。

1
在某些操作系统上,可以将其定义为“unsigned long”以适用于两种情况。 - user1143634

-5

size_t是指针的大小。

因此,在32位或常见的ILP32(整数、长整型、指针)模型中,size_t为32位。 而在64位或常见的LP64(长整型、指针)模型中,size_t为64位(整数仍为32位)。

还有其他模型,但这些是g++使用的(至少默认情况下)。


16
size_t并不一定与指针大小相同,尽管它通常是这样的。指针必须能够指向内存中的任何位置;而size_t只需足够大以表示最大单个对象的大小。 - Keith Thompson
intptr_t 的大小可能与 void * 指针相同。这不是必需的,但 intptr_t 必须能够容纳所有可能的有效值,以供 void * 指针使用。但是 size_t 没有这个要求。此外,size_t 至少为 16 位,而指针可以更小。 - 12431234123412341234123

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接