
2.1.4 字符串字面值
一个字符串字面值是一个包围在双引号中的零个或更多个字符的序列,比如"xyz"。宽字符串字面值除了以字母L作为前缀外,其他的表示方式与字符串字面值相同,比如L"xyz"。
在一个字符常量或字符串字面值中,在执行期间使用的字符集的成员,用源代码中字符集的相应成员表示,或由反斜杠(\)后跟一个或多个字符的转义序列表示。基本执行字符集必须存在一个所有位为0的字节,称为空字符,它用来终止字符串。
在编译期间,由相邻的字符和具有相同前缀的字符串字面量标记的任意序列指定的多字节字符序列,拼接成单个多字节字符序列。如果其中任何一个标记有编码前缀,那么由此产生的多字节字符序列就视为具有相同的前缀;否则,把它作为一个字符串字面值对待。不同前缀的宽字符串字面值的标记是否可以联接(并且,如果是这样,对由此产生的多字节字符序列的处理)是具体实现定义的。例如,下面的相邻字符串字面值的标记序列
"a" "b" L"c" "a" L"b" "c" L"a" "b" L"c" L"a" L"b" L"c"
都等于字符串字面值
L"abc"
接下来,把一个值为0的字节或编码附加到每个由一个或多个字符串字面值形成的字符序列的末尾。(一个字符字串字面值不一定是一个字符串,因为,其中可能会用一个\0的转义序列嵌入一个空字符。)该字符序列然后用于初始化一个持续静态存储的数组,其长度正好足以包含此序列。对于字符串字面值,数组中的元素类型为char,并用字符序列中的每个字节初始化。对于宽字符串字面值,数组中的元素类型为wchar_t,并用对应于该字符序列的宽字符序列进行初始化,这个宽字符序列是由一个实现定义的当前语言环境的mbstowcs()(多字节字符串到宽字符串)函数定义的。含有不在执行字符集中表示字符或转义序列的一个字符串字面值的值是具体实现定义的。
在C中,字符串字面值的类型是一个char数组,但在C++中,它是一个const char数组。因此,一个字符串字面值在C中是可修改的。然而,如果程序试图修改这样的一个数组,该行为是未定义的,因此这种行为是 《C安全编码标准》[Seacord 2008],“STR 30-C.不要试图修改字符串字面值”禁止的。制定这条规则的原因之一是,如果这些数组的元素有适当的值,在C标准中没有规定这些数组必须是不同的。例如,编译器有时会把多个相同的字符串字面值存储在相同的地址中,这样导致修改一个这样的字面值可能也会改变其他字面值。制定这条规则的另一个原因是,字符串字面值经常存储在只读存储器(ROM)中。
C标准允许在声明一个数组变量时,既包括界限索引又包括一个初始化字面值。初始化字面值也蕴含着一个数组大小,即其中指定的元素数量。对于字符串,一个字符串字面值指定的大小是字面值中的字符数再加上1(用于终止的空字符)。
数组变量常常由一个字符串字面值进行初始化,并且声明为一个与字符串字面值中的字符数目相匹配的显式界限。例如,下面的声明使用一个字符串字面值初始化了一个字符数组,此字面值比数组能容纳的字符多一个字符(包括终结符'\0'):
const char s[3] = "abc";
虽然字符串字面值的大小是4,但数组s的大小是3,因此,尾随的空字节被删除。任何随后将数组作为一个空字节结尾的字符串的使用都会导致漏洞,因为s没有正确地以空字符结尾。
一个更好的方法是,对于一个用字符串字面值初始化的字符串,不指定它的界限,因为编译器会自动为整个字符串字面值分配足够的空间,包括终止的空字符:
const char s[] = "abc";
因为即使字符串的字面值的大小变化了,数组大小总是可以获得的,所以这种方法还简化了维护工作。这一问题已被《C安全编码标准》[Seacord 2008]进一步描述为,“STR 36-C。不要指定一个用字符串字面值初始化的字符数组的界限。”