科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道基础软件VC++2005快速构建安全的应用程序

VC++2005快速构建安全的应用程序

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

本文针对微软的Visual C++2005发布版本中语言和库的一些新的特点进行了讨论,这将帮助你更高效地创作安全、可靠的代码。

作者:刘涛编译 来源:天极网 2007年11月16日

关键字:

  • 评论
  • 分享微博
  • 分享邮件
三、使用C++标准库

  已经看了C运行时库新增强的安全特性,让我们来看一下如何使用C++标准库来进一步减少你的代码中的相似错误。

  当你从C运行时库转向C++的标准库,让你从C++开始受益的一个最有效的方法是使用库中的矢量类(Vector class)。矢量类是C++标准库中的一个模仿一维T数组的容器类,这里T可以是事实上的任何类型,你的代码中使用缓冲区的地方都可以用矢量对象来代替。让我们来考虑上一节的例子,第一个例子我们使用gets_s函数来从标准输入中读取一个行,考虑用下面的代码代替:

std::vector<char> buffer(10);
gets_s(&buffer[0], buffer.size());

  最值得注意的一个区别是缓冲区变量现在是一个带有可用方法和操作符的矢量对象,这个矢量对象初始化为10个字节长度,并且构造函数将每个元素都初始化为0,表达式&buffer[0]用于得到矢量对象的第一个元素的地址,向期待一个简单缓冲区的C函数传递一个矢量对象是一种正确的方法。与sizeof操作符不同的是,所有的容器的尺寸测量是基于元素的,而不是基与字节的。例如,矢量的size方法返回的是容器的元素数量。

  在上节的第二个例子里,我们使用strcpy_s函数从源缓冲区向目标缓冲区拷贝字符。应该清楚矢量对象是如何代替原始的C类型的数组,为了更好的说明这一点,让我们来考虑另外一个非常有用的C++标准库的容器。

  提供的basic_string类使得字符串在C++中可以作为正常的类型来操作。它提供了各种各样的重载操作符,为C++程序开发人员提供了自然的编程模式。由于优于strcopy_s及其它操作字符串的函数,你应该首选basic_string函数。basic_string以字节为单位的T类型容器。这里T是字符类型。C++标准类库对于常用的字符类型提供类型定义。string和wstring中的元素类型分别被定义为char和wchar类型。下面的例子说明basic_string类是多么简单和安全:

std::string source = "Hello world!";
std::string destination = source;

  basic_string类也提供了你所希望的、常用的字符串操作的方法和操作符,象字符串联合及子串的搜索。

  最后,C++标准库提供了一个功能非常强大的I/O库,用来安全、简单地与标准输入输出、文件流进行交互操作。虽然对于gets_s函数来说使用矢量对象比使用C类型的数组更好,但你可以通过使用定义的basic_istream 和 basic_ostream类进一步简化。实际上,你可以书写简单并且类型安全的代码从流中来读取包括字符串在内的任何类型。

std::string word;
int number = 0;
std::cin >> word >> number;
std::cout << word
<< std::endl
<< number
<< std::endl;

  cin被定义成一个basic_istream流,从标准的输入中提取字符类型的元素。wcin是用于wchar_t元素。另一方面,cout被定义为一个basic_ostream流,用于向标准的输出流写入操作。正如你能想象的,这种模式比起gets_s和puts函数来可以无限的扩展。但是,真正的价值是在于它非常难以产生让你的应用程序出现安全裂痕的错误。

  四、C++标准库中的边界检查

  默认情况,C++标准库中大量的容器对象和迭代对象没有提供边界检查。例如,矢量的下标操作符通常是一个比较快,但有潜在的危险性的操作单独元素的方法。如果你正在寻找得到确认检查的操作方法,你可以转向"at"方法。安全性的增加是以牺牲性能为代价的。当然,绝大情况下性能的降低是可以忽略不计的,但是对于性能要求第一位的代码来说,这可能是非常有害的,思考一下下面的简单函数:

void PrintAll(const std::vector<int>& numbers)
{
 for (size_t index = 0; index < numbers.size(); ++index)
 {
  std::cout << numbers[index] << std::endl;
 }
}
void PrintN(const std::vector<int>& numbers, size_t index)
{
 std::cout << numbers.at(index) << std::endl;
}

  PrintAll函数使用了下标操作符,因为索引由函数控制,并且可以确认是安全的。另一方面,PrintN函数不能保证索引的有效性,所以它使用了更安全的"at"方法来代替。当然,并不是所有的容器的存取操作都象这么简洁明了。

  在保证C++标准库的安全特性的同时,Visual C++2005继续坚持并在很多情况下改进了C++标准库的运行特性,同时提供了调节C++标准库安全性的特色。一项受人欢迎的改进是在调试版本中添加了范围检查,这对你的发行版本性能并不构成影响。但这确实帮助你在调试阶段捕获越界错误,甚至是使用传统上不安全的下标操作符的代码。

  不安全的函数,象vector的下标操作算子,和其他的函数,象它的front函数,如果不恰当的调用,通常会导致不明确的行为。如果你幸运的话,它将很快导致一个存取冲突,这将使你的应用程序崩溃。如果你不那么走运的话,它可能默默地持续运转并导致不可预知的副效应,这将破坏数据并可能被进攻者利用。为了保护你的发行版本的应用程序,Visual C++2005引入了_SECURE_SCL符号,用来给那些非安全的函数添加运行时检查。象下面的代码那样在你的应用程序中简单地定义这个符号可以添加额外的实时检查并阻止不确切的行为。

#define _SECURE_SCL 1

  紧记定义这个符号对你的程序冲击很大,大量的合法的,但是具有潜在非安全的操作将在编译时将无法通过,以避免在运行时出现潜在BUG。思考下面的使用Copy运算的例子:

std::copy(first, last, destination);

  其中,first和last是定义拷贝范围的迭代参数,destination是输出迭代参数,指示了目标缓冲区的位置,这个位置用来拷贝范围之内的第一个元素。这里有一个危险是destination所对应的目标缓冲区或容器不足够大,无法容纳所要拷贝的元素。如果Destination是一个需要安全检查的迭代参数,类似的错误将被捕获。但是,这仅仅是一个假设。如果destination是一个简单的指针,将无法保证copy运算函数正确运转。这时当然会想到_SECURE_SCL符号来避免这一问题,这种情况下,代码甚至是不能编译,以此避免任何可能的运行时错误。就象你想象的那样,这将需要重写更完美有效的代码。所以,这是一个更好的理由支持C++标准库容器,避免使用C类型数组。
    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章