不当使用memset函数带来的麻烦问题
通常,在C编程中,我们经常使用memset函数来清除或设置一个连续的内存区域为其他指定值。最近移植一段java代码到C++时,memset函数使用不当,花了我几个小时调试。测试提示:关于虚函数的底层机制有很多详细的资料,这次调试我感触很深。
我们先来看一段代码。在继承的高级类中,有许多属性字段。Examada希望将它们清除为0或NULL,所以在构造函数中,Examada通过memset将当前类的所有属性设置为0。
class Base {
public:
virtual void kick off()= 0;
};
class Advance:public Base {
public:
Advance(){
memset(this,0,sizeof(Advance));
}
void kick off(){
count++;
//...做点别的;
}
private:
int attr 1,attr2
char * label;
int count;
//...其他属性,开始时应该初始化为0或NULL。
};
int _tmain(int argc,_ TCHAR * argv[])
{
Base * ptr = new Advance();
ptr->启动();
返回0;
}
这看起来工作正常,但运行程序时会发现类似如下的错误:
test virtual . exe中0x00415390处未处理的异常:0xC0000005:读取位置0x00000000时发生访问冲突
同时,断点停留在ptr-> kicken()。从错误提示中我们可以知道不能调用kick方法,这个方法的指针没有正确初始化。
在指出问题之前,先看看本文中对虚函数机制的解释:
函数所依赖的底层机制:vptr+vtable。虚函数的运行时实现采用VPTR/VTBL的形式。这项技术的基础:
①编译器在后台为每个包含虚函数的类生成一个静态函数指针数组(虚函数表)。这个类或其基类中定义的每个虚函数都有一个对应的函数指针。
②每个包含虚函数的类的每个实例都包含一个不可见的数据成员vptr(虚函数指针),它由构造函数自动初始化并指向该类的vtbl(虚函数表)
③当客户调用虚函数时,编译器生成回指vptr的代码并将其索引到vtbl中,然后在指定位置找到函数指针并发出调用。
这里的问题在于
Memset (this,0,sizeof(advance));
上面提到,虚函数指针在进入构造函数赋值体之前应该自动初始化,但是memset将初始化后的指针清零,这就是上面访问零地址错误发生的原因。去掉上面的memset语句,程序就可以正常运行了。
所以,从上面的问题可以看出,在构造函数体中调用memset将整个对象清零是非常冒险的。当没有虚函数时,上述程序可以正常运行(可以尝试将基类的纯虚函数声明改为非虚函数,然后运行程序)。初始化类的属性对象时,逐个手动初始化更安全。
0条评论