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

2.4.5 使字符串对象的引用失效

修改字符串的操作会使引用、指针和引用字符串对象的迭代器失效,这可能会导致错误。使用无效的迭代器是未定义的行为,并可能会导致安全漏洞。

例如,下面的程序片段试图清洗存储在input字符数组的e-mail地址,再将它传递给命令shell,方法是把以空字符结尾的字节串拷贝到一个字符串对象(email)中,同时把每个分号替换为空格字符,如下所示。


01  char input[];
02  string email;
03  string::iterator loc = email.begin();
04  // 
复制到 string
对象,同时把";" 
转换成 " "
05  for (size_t i=0; i < strlen(input); i++) {
06    if (input[i] != ';') {
07      email.insert(loc++, input[i]); // 
非法迭代器
08    }
09    else email.insert(loc++, ' '); // 
非法迭代器
10  }

这段代码的问题是第一次调用insert()后,迭代器loc失效,以后每次调用insert()都导致未定义的行为。如果程序员意识到这个问题,就可以很容易地修复这个问题,如下所示。


01  char input[];
02  string email;
03  string::iterator loc = email.begin();
04  // 
复制到 string
对象,同时把";" 
转换成 " "
05  for (size_t i=0; i < strlen(input); ++i) {
06    if (input[i] != ';') {
07      loc = email.insert(loc, input[i]);
08    }
09    else loc = email.insert(loc, ' ');
10    ++loc;
11  }

在这个版本的程序中,每次插入后,迭代器loc的值都被正确地更新,从而消除了未定义的行为。大部分被检查过的标准模板库(STL)实现都自动检测常见的错误。至少,在使用你的完整测试做预发布测试期间,在单一平台上使用一个被检查过的STL实现来运行你的代码。

basic_string类一般可以防止缓冲区溢出,但仍存在编程错误可能会导致缓冲区溢出的情况。虽然当操作引用字符串的范围之外的内存时,C++一般抛出一个std::out_of_range类型的异常,但是为了使效率最高,下标成员std::string::operator[](不执行边界检查)不会抛出异常。例如,下面的程序片段在f()>= bs.size()时会导致在bs字符串对象分配的存储空间的边界之外的一个写操作。


1  string bs("01234567"); 
2  size_t i = f(); 
3  bs[i] = '\0';

at()方法的行为方式与索引operator[]类似,但如果pos>=size(),它会抛出一个out_of_range异常,如下所示。


1  string bs("01234567"); 
2  try { 
3    size_t i = f(); 
4    bs.at(i) = '\0'; 
5  } 
6  catch (out_of_range& oor) { 
7    cerr << "Out of Range error: " << oor.what() << '\n'; 
8  }

虽然basic_string类一般更安全,但在一个C++程序中使用以空字符结尾的字节字符串一般是不可避免的,除了在罕见的情况下,其中既没有字符串字面值,又没有与接受以空字符结尾的字节字符串的现有库的相互作用。c_str()方法可以用来生成一个与字符串对象内容相同的以空字符结尾的字符序列,并把它作为一个字符数组的指针返回。


string str = x; 
cout << strlen(str.c_str());

c_str()方法返回一个const值,这意味着调用free()或delet返回的字符串都是一个错误。修改返回的字符串,也会导致一个错误,所以如果你需要修改字符串,就先复制一份,然后修改副本。