C和C++安全编码(原书第2版)
上QQ阅读APP看书,第一时间看更新

2.1.7 计算字符串大小

为防止缓冲区溢出和其他一些运行时错误,正确地计算字符串大小是必不可少的。使用不正确的字符串大小会导致缓冲溢出,例如,会分配一个大小不充足的缓冲区。《C安全编码标准》[Seacord 2008],“STR31-C.保证字符串的存储空间具有容纳字符数据和空终结符的足够空间”提到了这个问题。数组和字符串的几个重要属性,对于正确分配空间,并防止缓冲区溢出是至关重要的。

大小(size)

分配给数组的字节数(等于sizeof(array))。

计数(count)

在数组中的元素数目(等于在Visual Studio 2010中的_countof(array))。

长度(length)

在空终结符之前的字符数。

混淆这些概念经常会导致C和C++程序中的严重错误。C标准保证,类型为char的对象由单个字节组成。因此,一个字符数组的大小等于一个char数组的计数(这也是数组的界限)。长度是在空终结符之前的字符数。对于一个正确地以null结尾的char类型的的字符串,其长度必然是小于或等于其大小减1。

当宽字符串被误认为是窄字符串或多字节字符串时,可能会不正确地计算其大小。C标准定义的wchar_t是一个整数类型,其值的范围可以代表所支持的语言环境中最大的扩展字符集的所有成员的不同编码。Windows会使用UTF-16字符编码,所以wchar_t的大小通常为两个字节。Linux和OS X(GCC/g++以及Xcode中)使用UTF-32字符编码,所以wchar_t的大小通常为4个字节。在大多数平台上,wchar_t的大小至少是两个字节,因此,wchar_t数组的大小已不再等于对同一个数组的计数。作其他假定的程序可能包含错误。例如,在下面的程序片段中,错误地使用strlen()函数来确定一个宽字符串的大小:


1 wchar_t wide_str1[] = L"0123456789";
2 wchar_t *wide_str2 = (wchar_t *)malloc(strlen(wide_str1) + 1);
3 if (wide_str2 == NULL) {
4 /* handle error */
5 }
6 /* ... */
7 free(wide_str2);
8 wide_str2 = NULL;

当编译此程序时,Microsoft Visual Studio 2012将生成一个不兼容的类型警告并终止翻译。GCC4.7.2虽然也生成一个不兼容的类型警告但却能继续编译。

对一个以空字符结尾的字节字符串,strlen()函数对终止空字节前面的字符数量进行计数(长度)。然而,宽字符可以包含空字节,尤其是从ASCII字符集获取时,如在这个例子中。因此strlen()函数将返回在字符串中的第一个空字节前的字节数。

在下面的程序片段中,正确地使用wcslen()函数来确定一个宽字符串的大小,但此长度没有乘以sizeof(wchar_t)。


1  wchar_t wide_str1[] = L"0123456789";
2  wchar_t *wide_str3 = (wchar_t *)malloc(wcslen(wide_str1) + 1);
3  if (wide_str3 == NULL) {
4    /* 
处理错误 */ 
5  }
6  /* ... */
7  free(wide_str3);
8  wide_str3 = NULL;

下面的程序片段正确地计算了容纳宽字符串的一个副本所需的字节数(包括终止字符):


01  wchar_t wide_str1[] = L"0123456789";
02  wchar_t *wide_str2 = (wchar_t *)malloc(
03    (wcslen(wide_str1) + 1) * sizeof(wchar_t)
04  );
05  if (wide_str2 == NULL) {
06    /* 
处理错误 */
07  }
08  /* ... */
09  free(wide_str2);
10  wide_str2 = NULL;

《C安全编码标准》[Seacord 2008],“STR31-C.保证字符串的存储空间具有容纳字符数据和空终结符的足够空间”,正确地提供了关于计算宽字符串大小的补充信息。