小星星的天空

O(∩_∩)O 小月亮的fans ^_^

  C++博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  16 随笔 :: 0 文章 :: 61 评论 :: 0 Trackbacks
通常在C的编程中,我们经常使用memset函数将一块连续的内存区域清零或设置为其它指定的值,最近在移植一段java代码到C++的时候,不当使用memset函数花费了我几个小时的调试时间。对于虚函数的底层机制很多资料都有较详细阐述,但对我个人而言,这次的调试让我感触颇深。

先来看一段代码,在继承的类Advance之中,有很多属性字段,我希望将其清成0或NULL,于是在构造函数中我通过memset将当前类的所有属性置0。

class Base{

public:

virtual void kickoff() = 0;

};
class Advance:public Base{

public:

Advance(){

memset(this, 0, sizeof(Advance));

}

void kickoff(){

count++;

//... do something else;

}

private:

int attr1, attr2;

char* label;

int count;

//... other attributes, they should be initiated to 0 or NULL at beginning.

};

int _tmain(int argc, _TCHAR* argv[])

{
Base* ptr = new Advance();
ptr->kickoff();
return 0;
}

这样看似能正常运行,但运行程序时,你会发现类似于下面的错误:

TestVirtual.exe 中的 0x00415390 处未处理的异常: 0xC0000005: 读取位置 0x00000000 时发生访问冲突

同时断点停留在ptr->kickoff()处,从错误提示我们可以得知无法调用kickoff方法,这个方法的指针没有被正确初始化,但为什么呢?

指出问题之前,先看看这段文献上的关于虚函数机制的说明:

函数赖以生存的底层机制:vptr + vtable。虚函数的运行时实现采用了VPTR/VTBL的形式,这项技术的基础:
①编译器在后台为每个包含虚函数的类产生一个静态函数指针数组(虚函数表),在这个类或者它的基类中定义的每一个虚函数都有一个相应的函数指针。
②每个包含虚函数的类的每一个实例包含一个不可见的数据成员vptr(虚函数指针),这个指针被构造函数自动初始化,指向类的vtbl(虚函数表)
③当客户调用虚函数的时候,编译器产生代码反指向到vptr,索引到vtbl中,然后在指定的位置上找到函数指针,并发出调用。

这里的问题,就出在

memset(this, 0, sizeof(Advance));

上面,虚函数指针应该在进入构造函数赋值体之前自动初始化的,而memset却又将已经初始化好的指针清0了,这就是为什么会产生上面的访问零址的错误。将上面的memset语句去除程序就可以正常运行了。

所以,从上面的问题中,我们可以看出在构造函数体内调用memset将整个对象清0是很有风险的,当没有虚函数的时候上面程序可以正常运行(可以试着将Base类的纯虚函数声明改成非虚函数再运行程序)。初始化类的属性对象时,比较稳妥的办法还是手动逐个进行初使化
posted on 2009-10-20 21:11 Little Star 阅读(2778) 评论(7)  编辑 收藏 引用 所属分类: 找工作

评论

# re: 【转】不当使用memset函数带来的麻烦问题 2009-10-20 22:32 OwnWaterloo
没有虚函数也不可以乱来。
空指针并不一定是二进制全0。

1.
char* label = 0;

2.
char* label;
memset(&label,0,sizeof(label) );

有平台上两者功能不同。

  回复  更多评论
  

# re: 【转】不当使用memset函数带来的麻烦问题 2009-10-21 10:47 Little Star
谢谢!@OwnWaterloo
  回复  更多评论
  

# re: 【转】不当使用memset函数带来的麻烦问题 2009-10-21 21:11 周龙亭
在C++中不能用memset来初始化一个类。
你上面的的代码,其实是用写C代码的习惯来写C++代码,
推荐LZ全新的学习C++。  回复  更多评论
  

# re: 【转】不当使用memset函数带来的麻烦问题 2009-10-21 23:02 Little Star
@周龙亭

这个是我转的,不代表我的意见。
问题是如果说一个类有上千个属性的话,要咋清零呢?
  回复  更多评论
  

# re: 【转】不当使用memset函数带来的麻烦问题 2009-10-21 23:39 OwnWaterloo
@Little Star
class C {
/* data declaration */
public:
C() { memset(this,0,sizeof(*this); }
};

改为:
class C {
struct data {
/* data declaration */
} data_;
public:
C() { memset(&data_,0,sizeof(data_); }
};


还是需要注意memset( ... 0 ... );
不能保证: 指针是nullptr,浮点数是0.0, 0.0f, 0.0lf。
能保证:整数是0, 字符是null字符,即'\0'。

  回复  更多评论
  

# re: 【转】不当使用memset函数带来的麻烦问题[未登录] 2009-10-22 01:52 Little star
@OwnWaterloo
那,有谁能讲讲为什么不能保证:指针是nullptr,浮点数是0.0, 0.0f, 0.0lf呢?  回复  更多评论
  

# re: 【转】不当使用memset函数带来的麻烦问题 2009-10-22 02:40 OwnWaterloo
@Little star
标准就这么规定的。

《C 语言常见问题集》 5.14中介绍了一些古怪的空指针。
“至少PL/I, Prime 50 系列用段07777, 偏移0 作为空指针。
……
CDC Cyber 180 系列使用包含环(ring), 段和位移的48 位指针。多数用户
(在环11 上) 使用的空指针为0xB00000000000。
在旧的1 次补码的CDC 机器上
用全1 表示各种数据, 包括非法指针, 是十分常见的事情。
Symbolics Lisp 机器是一种标签结构, 它甚至没有传统的数字指针; 它使用
<NIL, 0> 对(通常是不存在的<对象, 偏移> 句柄) 作为C 空指针。


浮点如果是采用IEEE754, 0.0恰好是二进制全0。
但标准没有保证浮点数一定采用IEEE754。

  回复  更多评论
  


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   博问   Chat2DB   管理