请看下面这个结构体定义:
struct MyStruct
{
int i;
wstring str;
vector<wstring> strs;
};
上面的结构休定义有问题吗?理论上说没有,但在某些情况下就有问题了。如下用法:
MyStruct *pMy = new MyStruct();
memset(pMy, 0, sizeof(MyStruct));
pMy->i = 100;
pMy->str = L"leehong";
// Here will crash, vector is not POD, so memset() function or ZeroMemory macro
// do not know how to initialize the member, or it will lead some infos of pstr member
// lost and crash when access. pMy->pstr.push_back(L"One");
如果这样用的话,在Debug下运行没有问题,但在Release下运行,就会在push_back处Crash。这是为什么呢?这就引出下面要讲的POD了。
什么是POD
POD(Plain Old Data)指的是能够像C语言中的结构体那样进行处理的一种数据类型,比如能够使用memcpy()来复制内存,使用memset()进行初始化等。
在C++ 98标准中,POD实际上是受限于结构体定义中的语言特性而定义的。
struct S { int a; }; //S就是一个POD
struct SS { int a; SS(int aa) : a(aa) { } }; // SS就不是POD了
struct SSS { virtual void f(); };
在C++0x中,POD被定义为可以简简单单复制的,类型普通的,并且拥有可以应对多种POD原先就能支持的操作的标准变量地址布局(?)。POD的定义和以前差不多:
1)如果你所有的成员变量和基类都是POD,那么这个类型就是POD。
2)POD应当满足以下要求,
新标准对POD最大的影响就是,对于拥有不会影响数据分配布局的构造函数的数据结构,也可以算作是POD。
关于POD详细信息,请参考官方文档:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2294.html
Crash原因分析
回到最上面举的那个例子,结构体初始化时,首先会调用它自动生成的默认构造函数,同时也会调用vector<wstring>的构造函数为pstr成员生成一块内存。然后我们都知道,使用结构体一般都会用memset 或 ZeroMemory函数来把结构体清成0,那么当我们调用ZeroMemory后,之前为pstr生成的内存就变成了0,这样当在后面再访问这个成员时,就会导致Crash。
所以一般情况下在结构体中不要定义一些复杂的数据类型,如果无法避免,就声明这种类型的指针,因为指针肯定是一个POD的数据。
解决方法
1)上面的那种方法可以把pstr换成指针类型,也就是vector<wstring>* 在调用ZeroMemory后,再对这个变量分配内存。
2)为这个结构体写构造函数,在这个构造函数里面进行初始化,在使用时永远不要调用ZeroMemory,这样的话,struct就实际上跟class的用法一样了。