翻译自Dan Saks(7/3/2007 12:27 PM)《Why size_t matters

写在翻译前的话

unsigned int不是唯一的无符号整数类型。 size_t可以是unsigned charunsigned shortunsigned intunsigned longunsigned long long中的任何一个,具体取决于实现。

if it is use to represent non negative value so why we not using unsigned int instead of size_t .

前言

适当地使用size_t可以改善你代码的可移植性、高效性或者可读性。或许同时提高三者。

标准C库中的许多函数接受参数或返回以字节表示对象大小的值。例如,malloc(n)中的惟一参数指定要分配的对象的大小,memcpy(s1, s2, n)中的最后一个参数指定要复制的对象的大小。strlen(s)的返回值生成以null结尾的字符数组s的长度(其中的字符数),不包括null字符,它的大小并不完全等于s,但大致相同。

您可能合理地期望这些表示大小的参数和返回类型使用int类型声明(可能是long和/或unsigned),但是它们不是。然而,C标准将他们声明为size_t类型。根据标准规范,malloc的声明应该出现在<stdlib.h>等价于:

void *malloc(size_t n);

并且memcpystrlen的声明应该出现在<string.h>,看起来更像:

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代表什么,也不知道为什么库像他们那样使用size_t。此外,他们不知道自己是否以及何时应该使用size_t

在本专栏中,我将向你阐释size_t是什么,它为什么存在?你如何在自己的代码中运用它?

一个可移植性的问题

经典C(Brian Kernighan 和 Dennis Ritchie 在 “The C Programming Language, Prentice-Hall, 1978”描述的早期C方言)没有提供size_t。C标准委员会引入size_t来消除可移植性问题,如下例所示。

让我们通过写一个简洁的标准memcpy函数来测试一下这个问题。我们将研究一些不同的声明,并查看它们在具有不同大小的地址空间和数据路径的不同体系结构下编译时是如何工作的。

回想一下,调用memcpy(s1, s2, n)将前n个字节从s2指向的对象复制到s1指向的对象,并返回s1。此函数可以复制任何类型的对象,所以指针参数和返回值应该被声明为“指向void的指针”。并且memcpy不会更改源对象,所以第二个对象应该是“指向const void的指针”。这些都不是问题。

真正需要关心的是如何区分声明此函数的第三个表示源对象大小的参数,我怀疑许多程序员都会选择普通的int类型,比如:

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

使用int类型在大部分情况下都是可以的,但是并不是所有情况下都可以。普通的int是有符号的——它可以表示负值。然而,大小的n是永远不可能为负值的。在不造成额外浪费的情况下,使用unsigned int代替int作为memcpy的第三个参数可以赋值更大的对象。

在大多数机器上,最大的unsigned int值大约是最大的正int的两倍。例如,在16位双补码机器上,最大的unsigned int值是65,535,最大的正int值是32,767。使用unsigned int作为memcpy的第三个参数,可以复制比使用int大两倍的对象。

尽管在C实现中int的大小各不相同,但是在任何给定的实现上int对象的大小总是与unsigned int对象相同。因此,传递unsigned int参数的代价总是与传递int相同。

使用unsigned int作为参数类型,如:

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

在任何平台上都可以很好地工作,其中unsigned int可以表示最大数据对象的大小。在任何整数和指针大小相同的平台上,通常都是这样,比如IP16,其中整数和指针都占16位,或者IP32,两者都占32位。(请参阅侧栏关于C数据模型符号。)

C数据模型表示法
最近,我读了几篇文章,它们使用了一种紧凑的表示法来描述不同目标平台上的C语言数据表示。我还没有找到这个符号的起源,一个正式的语法,甚至它的名字,但是它看起来足够简单,不需要一个正式的定义就可以使用。表示法的一般形式似乎是:
InI LnL LLnLL PnP
其中,每个(或其两个)大写字母表示一个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。
标记通常把字母分类在一起,所以可以按照其对应的数字升序排列。例如,IL32LL64P32表示具有32位int、32位long、64位long long和32位指针的体系结构;然而,它更常见的名称是ILP32LL64。

不幸的是,memcpy的声明在I16LP32处理器(16位用于int, 32位用于long和指针)上出现不足,比如第一代摩托罗拉68000。在本例中,处理器可以复制大于65,536字节的对象,但是这个memcpy不能,因为参数n不能处理那么大的值。

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

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

您可以使用此声明为I16LP32目标编写memcpy,它将能够复制大型对象。它还将在IP16和IP32平台上工作,因此它确实为memcpy提供了一个可移植的声明。不幸的是,在IP16平台上,这里使用unsigned int得到的机器代码几乎肯定比使用unsigned int得到的效率要低一些(代码既大又慢)。

在标准C中,long(无论是带符号的还是无符号的)必须至少占用32位。因此,支持标准C的IP16平台必须是IP16L32平台。这样的平台通常实现每一个32位长的16位的块。在这种情况下,移动一个32位的数据块通常需要两个机器指令,一个机器指令移动每个16位的数据块。事实上,这些平台上几乎所有32位操作都至少需要两条指令,如果不是更多的话。

因此,以可移植性的名义将memcpy的第三个参数声明为unsigned long会对某些平台造成性能损失,这是我们希望避免的。使用size_t可以避免这种代价。

类型size_t是一个类型定义,它是一些unsigned int类型的别名,通常是unsigned intunsigned 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_tsizeof关键字(注:sizeof是关键字,并非运算符)运算结果的类型。所以,应当通过适当的方式声明n来完成赋值:

n = sizeof(thing);

可移植且高效的方法是使用size_t类型声明foo的参数。参数类型为size_t的函数通常有一些局部变量,这些局部变量的大小和索引都在数组中,而size_t通常是这些变量的好类型。

适当地使用size_t可以使您的源代码更加自文档化。当您看到一个对象声明为size_t时,您立即知道它表示的是以字节或索引为单位的大小,而不是错误代码或一般算术值。

希望在以后的专栏文章中看到我在其他示例中使用size_t

Dan Saks is president of Saks & Associates, a C/C++ training and consulting company. For more information about Dan Saks, visit his website at www.dansaks.com. Dan also welcomes your feedback: e-mail him at [email protected]. For more information about Dan click here .