Articles

VC6 stream类存在崩溃的隐患

在开发过程中,发现一个奇怪的问题,程序用VC6可以顺利编译通过,但运行的时候却会崩溃。示例代码如下:

#include <sstream>
#include <vector>
using namespace std;

int main(int argc, char* argv[])
{
  stringstream Stream;
  Stream << "123";

  vector<stringstream> vecStreams;
  vecStreams.push_back(Stream);

  return 0;
}

Debug版本运行结果:

stream_crash

最初是怀疑代码中其他地方指针使用有误,导致内存分配出现问题。所以把关键代码单独提出来,写了上述Demo程序,依然崩溃。所以怀疑VC6提供的C++库本身有问题,或者是用法有问题,因为VC6并不能很好的支持C++标准。于是,使用VC2008重新编译上述代码,无法编译通过,错误提示如下:

Microsoft Visual Studio 9.0\VC\include\sstream(516) : error C2248:
‘std::basic_ios<_Elem,_Traits>::basic_ios’ : cannot access private member declared in class ‘std::basic_ios<_Elem,_Traits>‘

std::basic_ios::basic_ios为什么会是private的呢?难道所有从basic_ios派生的类都不允许被拷贝吗?可是VC6为什么又能编译通过呢?于是我查阅了ISO/IED 14882:1998 C++标准,里面这样写到:

27.4.4 Template class basic_ios private: basic_ios(const basic_ios&); // not defined basic_ios& operator=(const basic_ios&); // not defined

可见VC2008与C++标准是一致的,把basic_ios的拷贝构造、赋值操作都声明为私有的。VC6正是因为没有很好的支持C++标准,导致上述代码被错误的编译,出现错误的结果。

但是,为什么C++标准中要把这两个函数声明为私有呢?Apache C++ Standard Library User’s Guide里面说出了两个主要的理由:

  1. 如果可以允许stream类间相互拷贝,那么std::cout,std::cin,std::cerr这样预先定义的特殊对象将会被赋值。但事实上,这些类是被特殊对待的,初始化的时候使用了一些特殊的参数。如果被赋值为别的stream对象,这些特殊的属性将会消失。
  2. 所有stream对象都有一个内存缓冲,这个内存缓冲是与打开的文件或者终端一一对应的。如果允许stream类间相互拷贝,就会导致多个stream类可能共享同一个内存缓冲,那么最后到底应该由哪个类来释放这个缓冲区呢?

所以永远都不应该拷贝stream对象。 为了验证上述理论的正确性,我们再分析一下C++中所有stream对象间的派生关系,如图所示:

stream

typedef basic_ios<char>                 ios;
typedef basic_istream<char>             istream;
typedef basic_ostream<char>             ostream;
typedef basic_iostream<char>            iostream;
typedef basic_istringstream<char>       istringstream;
typedef basic_ostringstream<char>       ostringstream;
typedef basic_stringstream<char>        stringstream;
typedef basic_ifstream<char>            ifstream;
typedef basic_ofstream<char>            ofstream;
typedef basic_fstream<char>             fstream;

经过测试,这10个类以及相应的宽字符版本10个类,以及basic_开头的这些类本身总共30个类在VC6中都存在同样的bug。对于这一问题的解决办法很简单,只要不使用对象拷贝就可以了。例如可以保存stream对象的指针vector vecStreams。

此外,VC中还有一个特殊的类strstream。这个类是直接从iostream派生的,而且没有相应的宽字符版本。

strstream

这个类并不是C++标准的一部分,只是微软自己早期的实现。在最新的MSDN中说,该类已经被废弃,其而代之应该使用的是stringstream和wstringstream。