为什么要用size_t?

C/C++ 3337℃

作者:Calix

本文在《Why size_t matters》文章的基础上进行翻译和重新组织,尽量用更通俗易懂的语言。

合理地使用size_t可以提高代码可移植性、执行效率以及可读性。


C语言标准库里面经常会看到这样的函数:接收一个代表“字节大小”的值作为参数,或者返回一个代表“字节大小”的返回值。

例如:

  • 函数malloc(n),其中的参数n表示需要分配“多少字节”的内存大小。
  • 函数memcpy(s1, s2, n),其中的参数n表示需要复制的“字节大小”。
  • 函数strlen(s)的返回值表示字符串的“长度大小”。

但是,表示大小为什么不直接用int类型,而非得搞个size_t?

上述几个函数的声明在标准库分别为:
    void *malloc(size_t n);
    void *memcpy(void *s1, void const *s2, size_t n);
    size_t strlen(char const *s);

用size_t的区别在哪? 很多人都知道标准库里经常用size_t,却搞不懂是什么意思,也不知道自己在写代码的时候该不该用。

希望接下来的内容能够帮助你解决上面的疑问。

可移植性问题

其实早期的C语言并没有size_t类型,后来C标准委员会为了解决一个可移植性问题而引入size_t。

我们通过一个例子来认识这个可移植性问题:假设让你来写一个memcpy函数,你会怎么写它的函数原型?

回忆一下函数memcpy(s1, s2, n)的作用,主要是把指针s2指向的内容复制到s1指向的地方,然后返回指针s1。而指针s1, s2可以指向任意类型,所以它们的类型是”void *”,而s2是被复制的,一般不允许被修改,s2可以更严格地声明为void const *s2。前面两个参数的类型都比较好确定,比较麻烦的是第三个表示复制大小的参数n,没事,我们先拍脑袋用int,所以最终函数原型是:

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

其实上面这个声明也是没什么大问题的,但是第一个纠结的地方来了,第三个参数n代表的是复制的大小,永远不可能为负数,而int是允许负数的,有一半的数字被浪费了,明显用unsigned int会更好(同样的字节数),有效的数字多了一倍,从语义也更合理。于是,声明变为:

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

这个声明一般情况下在绝大多数平台都是没什么问题的。

为了下面更好的阐述数据类型在不同平台之间的差异,引入一种简单的标记法“C数据模型标记法”:

C数据模型标记法

格式为:
size_t_1

其实很简单:

  • I代表int类型,L代表long类型,LL代表long long类型,P代表指针类型。
  • 类型下面各自的下标分别代表它们自各所占的位(bit)数。
  • 例如:
    2222

    表示这个体系结构中,int类型4个字节(32 bit),long类型4个字节,long long类型8个字节,指针4个字节。

    不过,一般先按下标大小排个序:

    33333

    然后,如果是有连续相同位数的类型会省略前面类型的下标只留后面一个类型的下标,比如上面的int、long、指针都是32位,则最终修改为:

    44444


好了,回到我们的函数声明:

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

假设有个处理器体系结构数据模型为I16LP32,而我们的函数声明用了unsigned int,则这个处理器将无法使用我们的memcpy复制大于65536(2的16次方)字节的内容,而明明它最大的数值就可以用32位的long来表示。

马上会有人说,那换成unsigned long啊:

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

确实这样能在I16LP32的处理器上很好的使用起来,但是作为一个标准库函数,必须要考虑它也会在其它平台上运行,比如”IP16**”, 这个还没标L的标记,指的是处理器是16位的,而标准C规定long类型至少4个字节(即32位),所以这样的平台想要支持标准C,”IP16**”就必须至少是”IP16L32″,被迫支持4个字节32位的long,在这种情况下传输一个32位long类型的数据,需要至少执行两个机器指令(因为每次最大只能传16位),所以这个时候会带来执行效率上的问题。

正因为上述的原因,所以需要引入size_t。

size_t实质上只是一个typedef,通常被typedef成unsigned int或unsigned long或unsigned long long。不同的平台会根据自己能表示的最大值而选择实现不同的typedef而已。

使用size_t

在C里面,size_t往往被定义在 <stddef.h>, <stdio.h>, <stdlib.h>, <string.h>, <time.h> 和 <wchar.h>里面; 而C++,会在<cstddef>, <cstdio>等等,使用的时候需要把至少其中一个inlcude进来。

从定义角度来说,size_t很适合来充当sizeof的运算结果。

比如:n = sizeof(thing); 这里的n就很适合声明为size_t类型。

所以size_t的引入,不仅仅解决了代码移植性和执行效率上的问题,还从一定程度上增强了代码的可读性。

转载请注明:Calix » 为什么要用size_t?

喜欢 (3)or分享 (0)
发表我的评论
取消评论
表情

亲~ 写下昵称哦~

  • 昵称 (必填)
  • 网址