关于size_t

size_t存在的意义

三个字:

跨平台

代码解释

size_t = typeof(sizeof(X))

简而言之

不同平台的size_t会用不同的类型实现,使用size_t而非int或unsigned可以写出扩展性(可移植性)更好的代码。

为什么要加“_t”?

_t的意思显然就是type。一个类型后面加了_t说明了这是一个POSIX或GNU保留类型,防止namespace pollution(类型名与变量名共享相同的命名空间)。POSIX规定自己扩展的类型都加_t,选择这样的命名形式作为编码规范,在命名方面刻意区分出来,这样用户自定义类型名的时候,只要不加_t,就不会与标准库已有的类型名冲突。

General Information(B.2.12)

Defined Types
The requirement that additional types defined in this section end in "_t" was prompted by the problem of name space pollution. It is difficult to define a type (where that type is not one defined by POSIX.1-2008) in one header file and use it in another without adding symbols to the name space of the program. To allow implementors to provide their own types, all conforming applications are required to avoid symbols ending in "_t", which permits the implementor to provide additional types. Because a major use of types is in the definition of structure members, which can (and in many cases must) be added to the structures defined in POSIX.1-2008, the need for additional types is compelling.
The types, such as ushort and ulong, which are in common usage, are not defined in POSIX.1-2008 (although ushort_t would be permitted as an extension). They can be added to <sys/types.h> using a feature test macro (see POSIX.1 Symbols). A suggested symbol for these is _SYSIII. Similarly, the types like u_short would probably be best controlled by _BSD.

知识科普

int小于等于数据线宽度,size_t大于等于地址线宽度

地址线宽度历史中经常都是大于数据线宽度的

在数据只有8位的年代,地址率先进入10位,12位,在数据16位的年代,地址也已经进入了20位(8086处理器),24位。

目前的int普遍是32位,而size_t在主流平台中都是64位。

什么是地址空间?

由于地址线只有$20$位,那么CPU地址线上只有$2$的$20$次方种可能的地址值,即8086 CPU的寻值范围是$2^{20}$。  

由于内存的一个数据单元是$8$位, 即$8 bit = 1 Byte$。一个地址对应一个内存数据单元。 所以,  $2^{20} * 1 Byte = 1MB$。 这就是为什么说8086的地址空间是 $1MB$。

数据线和地址线

由于 数据线宽度是16位, 而地址线是20位的。所以一个数据线16位的数据 并不能直接对应一个20位的地址值。需要通过一定的转换

为什么有“X86-32bit处理器支持内存不能超过4G” 这一说法?

因为地址线只有$32$位,那么, 最大的内存空间就只有$2^{32} Byte =  4 GB$。  

而 如果换成$64$位的, 那么, 最大的内存空间就是$2^{64} Byte = 2^{34} GB = (一个很大数字) $。

一句话本质

size_t和unsigned int有所不同,size_t的取值range是目标平台下最大可能的数组尺寸
最典型的,在x64下,int还是4,但size_t是8。这意味着你在x64下最大可能开辟的数组尺寸是$2^{64}$。

如果你使用int或者unsigned int,那么在x64下如果你的代码中全部使用uint作为数组的尺寸标记,

那么你就会失去控制$2^{32}$尺寸以上的数组的机会.虽然现在在x64上开辟一个大于$2^{32}$大小的连续数组依然是个不大可能的事情……

展开来细说

转载,说的很棒!为什么size_t(重要Why size_t matters

    之前在《内存拷贝的注意事项》一文中提到过size_t,可能许多人对这个类型不太熟悉没有用过或者根本不敢去用,最近看到一篇文章对这个类型讲的比较详细,便翻译过来让不熟悉的同学可以知道它产生的原因以及如何使用。    原文地址: Why size_t matters(链接试了找不到?)


前言

使用size_t可能会提高代码的可移植性、有效性或者可读性,或许同时提高这三者。

在标准C库中的许多函数使用的参数或者返回值都是表示的用字节表示的对象大小,比如说malloc(n) 函数的参数n指明了需要申请的空间大小,还有memcpy(s1, s2, n)的最后一个参数,表明需要复制的内存大小,strlen(s)函数的返回值表明了以’\0’结尾的字符串的长度(不包括’\0’),其返回值并不是该字符串的实际长度,因为要去掉’\0’。

或许你会认为这些参数或者返回值应该被申明为int类型(或者long或者unsigned),但是事实上并不是。C标准中将他们定义为size_t。标准中记载malloc的申明应该出现在,定义为:

void *malloc(size_t n);

memcpy和strlen的申明应该出现在中:

void *memcpy(void *s1, void const *s2, size_t n);
size_t strlen(char const *s);

size_t还经常出现在C++标准库中,此外,C++库中经常会使用一个相似的类型size_type,用的可能比size_t还要多。

据我所知,大部分的C和C++程序员害怕这些库使用size_t,因为他们不知道size_t代表什么或者为什么这些库需要使用它,归根结底,原因在于他们什么时候什么地方需要用到它。

可移植性问题

早期的C语言(由Brian Kernighan 和 Dennis Ritchie 在The C Programming Language书中所写,Prentice-Hall, 1978)并没有提供size_t类型,C标准委员会为了解决移植性问题将size_t引入,举例如下:

让我们来写一个可移植的标准memcpy函数,我们将会看到一些不同的申明和它们在不同平台不同大小的地址空间上编译下的情况。回忆memcpy(s1, s2, n)函数,它将s2指向地址开始的n个字节拷贝到s2指向的地址,返回s1,这个函数可以拷贝任何数据类型,所以参数和返回值的类型应该为可以指向任何类型的void*,同时,源地址不应该被改变,所以第二个参数s2类型应该为const void*,这些都不是问题。

真正的问题在于我们如何申明第三个参数,它代表了源对象的大小,我相信大部分程序员都会选择int:

void *memcpy(void *s1, void const *s2, int n);

使用int类型在大部分情况下都是可以的,但是并不是所有情况下都可以。int是有符号的,它可以表示负数,但是,大小不可能是复数。所以我们可以使用unsigned int代替它让第三个参数表示的范围更大。在大部分机器上,unsigned int的最大值要比int的最大值大两倍,比如说再也给16位的机器上,unsigned int的最大值为65535,int的最大值为32767。

尽管int类型的大小依赖于C编译器的实现,但是在给定的平台上int对象的大小和unsigned int对象的大小是一样的。因此,使用unsigned int修饰第三个参数的代价与int是相同的:

void *memcpy(void *s1, void const *s2, unsigned int n);  

这样似乎没有问题了,unsigned int可以表示最大类型的对象大小了,这种情况只有在整形和指针类型具有相同大小的情况下,比如说在IP16中,整形和指针都占2个字节(16位),而在IP32上面,整形和指针都占4个字节(32位)。(参见下面C数据模型表示法)

C数据模型表示法

最近,我偶然发现几篇文章,他们使用简明的标记来表述不同目标平台下c语言数据的实现。我还没有找到这个标记的来源,正式的语法,甚至连名字都没有,但他似乎很简单,即使没有正规的定义也可以很容易使用起来。这些标记的一边形式形如:

I nI L nL LL nLL P nP。

其中每个大写字母(或成对出现)代表一个C的数据类型,每一个对应的n是这个类型包含的位数。I代表int,L代表long,LL代表long long,以及P代表指针(指向数据,而不是函数)。每个字母和数字都是可选的.例如,I16P32架构支持16位int和32位指针类型,没有指明是否支持long或者long long。如果两个连续的类型具有相同的大小,通常省略第一个数字。例如,你可以将I16L32P32写为I16LP32,这是一个支持16位int,32位long,和32位指针的架构。

标记通常把字母分类在一起,所以可以按照其对应的数字升序排列。例如,IL32LL64P32表示支持32位int,32位long,64位long long和32位指针的架构;然而,通常写作ILP32LL64。

不幸的是,这种memcpy的申明在I16LP32架构上(整形是16-bit 长整形和指针类型时32-bits)显得不够用了,比如说摩托罗拉第一代处理器68000,在这种情况下,处理器可能拷贝的数据大于65535个字节,但是这个函数第三个参数n不能处理这么大的数据。

什么?你说很容易就可以改正?只需要把memcpy的第三个参数的类型修改一下:

void *memcpy(void *s1, void const *s2, unsigned long  n); 

你可以在I16LP32目标架构上使用这个函数了,它可以处理更大的数据。而且在IP16和IP32平台上效果也还行,说明它确实给出了memcpy的一种移植性较好的申明。但是,在IP16平台上相比于使用unsigned int,你使用unsigned long可能会使你的代码运行效率大打折扣(代码量变大而且运行变慢)。

在标准C中规定,长整形(无论无符号或者有符号)至少占用32位,因此在IP16平台上支持标准C的话,那么它一定是IP16L32 平台。这些平台通常使用一对16位的字来实现32位的长整形。在这种情况下,移动一个长整形需要两条机器指令,每条移动一个16位的块。事实上,这个平台上的大部分的32位操作都需要至上两条指令。因此,以可移植性为名将memcpy的第三个参数申明为unsigned long而降低某些平台的性能是我们所不希望看到的。

使用size_t可以有效避免这种情况。size_t类型是一个类型定义,通常将一些无符号的整形定义为size_t,比如说unsigned int或者unsigned long,甚至unsigned long long。每一个标准C实现应该选择足够大的无符号整形来代表该平台上最大可能出现的对象大小。

使用size_t

size_t的定义在<stddef.h>, <stdio.h>, <stdlib.h>, <string.h>, <time.h>和<wchar.h>这些标准C头文件中,也出现在相应的C++头文件, 等等中,你应该在你的头文件中至少包含一个这样的头文件在使用size_t之前。

包含以上任何C头文件(由C或C++编译的程序)表明将size_t作为全局关键字。包含以上任何C++头文件(当你只能在C++中做某种操作时)表明将size_t作为std命名空间的成员。

根据定义,size_t是sizeof关键字(注:sizeof是关键字,并非运算符)运算结果的类型。所以,应当通过适当的方式声明n来完成赋值:

n = sizeof(thing);

 考虑到可移植性和程序效率,n应该被申明为size_t类型。类似的,下面的foo函数的参数也应当被申明为sizeof:

foo(sizeof(thing));

参数中带有size_t的函数通常会含有局部变量用来对数组的大小或者索引进行计算,在这种情况下,size_t是个不错的选择。

适当地使用size_t还会使你的代码变得如同自带文档。当你看到一个对象声明为size_t类型,你马上就知道它代表字节大小或数组索引,而不是错误代码或者是一个普通的算术值。

参考资料:

https://www.zhihu.com/question/24773728

https://blog.csdn.net/kevin_ut/article/details/6313043

posted @ 2020-11-12 16:02  箐茗  阅读(168)  评论(0编辑  收藏  举报