请看下面这个结构体定义:
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 may 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->strs.push_back(L"One"); |
如果这样用的话,在Debug下运行没有问题,但在Release下运行,就会在push_back处Crash。这是为什么呢?这就引出下面要讲的POD了。
注:实际上并不是每一种编译器编译后的程序执行都会,实测gcc就不会。
什么是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(); }; // SSS不是POD |
在C++0x中,POD被定义为可以简单复制的,类型普通的,并且拥有可以应对多种POD原先就能支持的操作的标准变量地址布局(?)。POD的定义和以前差不多:
1)如果你所有的成员变量和基类都是POD,那么这个类型就是POD。
2)POD应当满足以下要求,
a)没有虚函数
b)没有虚基类
c)没有引用
d)没有多重访问
新标准对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的用法一样了。