
2.1.1 字符串数据类型
字符串由一个以第一个空(null)字符作为结束的连续字符序列组成,并包含此空字符。一个指向字符串的指针实际上指向该字符串的起始字符。字符串长度指空字符之前的字节数,字符串的值则是它所包含的按顺序排列的字符值的序列。图2.1显示“hello”的字符串表示形式。
图2.1 “hello”的字符串表示形式
字符串实现为字符数组而且容易遭受与数组同样的问题。
因此,适用于数组的安全编码实践也应当应用于以空字符结尾的字符串,参见《C安全编码标准》[Seacord 2008]的“数组(ARR)”一章。在处理数组时,定义一些术语很有用,如下所示。
界限(Bound)
数组中的元素个数。
低位地址(Lo)
数组首元素地址。
高位地址(Hi)
数组末元素地址。
TooFar
数组最远端的元素之后加1位置的元素地址,这个元素正好在Hi元素之后。
目标大小(Tsize)
与sizeof(array)相同。
C标准允许创建指向数组对象的末元素之后加1位置的指针,虽然这些指针无法在不产生未定义行为的状况下解引用。在处理字符串时,以下额外的术语也很有用:
空字符结尾(Null-terminated)
在Hi或它之前,存在空终结符。
长度(Length)
空终结符之前的字符数量。
数组大小。数组带来的问题之一是确定其元素数量。下面例子中,函数clear()使用惯用的sizeof(array)/sizeof(array[0])来确定数组中的元素数量。但是,因为array是一个参数,所以它的类型是指针。 [1]因此,sizeof(array)等于sizeof(int *)。例如,在sizeof(int)==4且sizeof(int *)==4的架构(如x86-32)中,无论传入的数组的长度是多少,表达式sizeof(array)/sizeof(array[0])的计算结果都为1,数组的其余部分不影响此结果。
01 void clear(int array[]) { 02 for (size_t i = 0; i < sizeof(array) / sizeof(array[0]); ++i) { 03 array[i] = 0; 03 array[i] = 0; 04 } 05 } 06 07 void dowork(void) { 08 int dis[12]; 09 10 clear(dis); 11 /* ... */ 12 }
这是因为sizeof运算符在应用于声明为数组或函数类型的参数时,它产生调整后的(即指针)类型大小。strlen()函数可以用来确定一个正确地以空字符结尾的字符串的长度,但不能用来确定一个数组的可用空间。《C安全编码标准》[Seacord 2008]涉及“ARR01-C.在获取一个数组的大小时,不要对一个指针应用sizeof运算符”,该规则针对这个问题提出了警告。
一个字符串中的字符都属于在执行环境中解释的字符集—执行字符集。这些字符由C标准定义的一个基本字符集和一组零个或多个扩展字符(它们不是基本字符集的成员)组成。执行字符集的成员的值是具体实现定义的,但可能(例如)是美国7位ASCII字符集的值。
C使用一个语言环境(locale)的概念,它可以由setlocale()函数改变,用来跟踪各种约定,如具体实现支持的语言和标点符号。当前语言环境确定哪些字符可用作可扩展字符。
基本执行字符集包括拉丁字母表的26个大写字母和26个小写字母、10个十进制数字、29个图形字符、空格字符,以及表示水平制表符、垂直制表符、换页符、警告、退格键、回车和换行符的控制字符。基本字符集的每个成员都适合用单个字节表示。基本执行字符集中必须存在一个字节的所有位都设置为0的字符,称为空字符(null),它是用来终止字符串的。
执行字符集可能包含大量的字符,因此需要多个字节来表示扩展字符集中的一些单个字符。这就是所谓的多字节(multibyte)字符集。在这种情况下,基本字符仍然必须存在,并且基本字符集的每个字符都编码为单字节。任何额外字符的存在、含义和表示都是特定于语言环境的。一个字符串,可能有时会称为一个多字节字符串,以强调它可能会存在多字节字符。这些与宽字符串不同,因为在宽字符串中每个字符都具有相同的长度。
一个多字节字符集,可能有状态相关(state-dependent)的编码,其中每个多字节字符序列开始于初始变换状态,并当在序列中遇到特定的多字节字符时进入其他特定语言环境的变换状态。当在初始变换状态中,所有的单字节的字符保留它们通常的含义,并且不变更变换状态。在序列中后续字节的含义是当前变换状态的一个函数。