Chinaunix首页 | 论坛 | 博客
  • 博客访问: 818401
  • 博文数量: 756
  • 博客积分: 40000
  • 博客等级: 大将
  • 技术积分: 4980
  • 用 户 组: 普通用户
  • 注册时间: 2008-10-13 14:40
文章分类

全部博文(756)

文章存档

2011年(1)

2008年(755)

我的朋友

分类:

2008-10-13 16:10:50

最近写一个使用完成端口的应用时, 居然在最简单的类型转换上栽了一个跟头, 写出来与大家分享,以避免犯和我类似的错误。
为了能尽量统一的处理每一个io操作, 我定义了下面这个类:
class CIoPacket : public OVERLAPPED
{
public:
    
virtual void OnIoComplete( ULONG_PTR key, DWORD dwBytes ) = 0;
    
virtual void OnIoFailure( ULONG_PTR key, DWORD dwBytes ) = 0;
}
;
其中的两个虚函数分别在io操作成功完成或失败后被调用. 它的派生类将用于记录每一次io的相关信息. 为了从网络上接收数据, 我有从它派生出了一个类:
class CNetPkt : public CIoPacket
{
protected:
    
// 定义一些成员变量
public:
    
virtual void OnIoComplete( ULONG_PTR key, DWORD dwBytes )
    
{
        
// 执行一些操作
    }

    
virtual void OnIoFailure( ULONG_PTR key, DWORD dwBytes )
    
{
        
// 执行一些操作
    }

}
;

下面的代码启动了一个接受数据的操作:
//
SOCKET sck;
// 初始化sck并绑定到一个完成端口
CNetPkt* pPkt = new CNetPkt();
// 设置pPkt的相关字段
::WSARecv( sck, &wsabuf, 1&dwBytes, &dwFlags, pPkt, NULL );                // (0)
//
下面是完成端口线程中的代码:
DWORD dwBytes = 0;
ULONG_PTR key 
= 0;
LPOVERLAPPED pol 
= NULL;

if( ::GetQueuedCompletionStatus(g_hIocp, &dwBytes, &key, &pol, INFINITE) )
{
    
if( key != 0 )
        reinterpret_cast
<CIoPacket*>(pol)->OnIoComplete( key, dwBytes );    // (1)
}

else
{
    
if( pol != NULL )
        reinterpret_cast
<CIoPacket*>(pol)->OnIoFailure( key, dwBytes );        // (2)
}

    自我感觉实现的既灵活又漂亮还健壮、高效(嗯?!哪里来的西红柿?味道还不错!). 但是程序每次运行到(1)或(2)时就会出现非法操作, 令我百思不得其解. 首先仔细检查程序, 没发现错误; 又使出十八般调试功夫, 还是没有找到问题所在. 正在头大之时, 突然发现(1)处的pol和(0)处的pPkt的值并不一样, pol比pPkt大了4, 进一步通过反汇编发现(0)处实际传给WSARecv的就是((LPBYTE)pPkt)+4. 两个值不一样, 总出错也就不奇怪了.
    可编译器为什么要给指针加上4呢? 难道是编译器把CIoPacket的vfptr放到了OVERLAPPED的前面? 可是我记得vc应该是把它放在后面的呀(具体不敢确定了, 但好像VC6就是放在后面). 一番测试证实了我的猜测, vc7.1就是会把vfptr放到类结构的最前面, 该死的微软居然偷偷改了这么重要的编译细节. 但也不能光骂微软, 自己的错误也要检讨一下, 上面的程序中我应该用static_cast, 而不是reinterpret_cast, 因为static_cast能正确调整基类和派生类的指针, 而reinterpret_cast从汇编的角度看是什么也不干的. 如果vfptr放在后面, static_cast和reinterpret_cast的结果是一样的, 但当vfptr放在前面的时候它们就不同了. 由于C++标准没有规定vfptr的放置位置, 所以大家进行类型转换时一定要注意选择正确方式, 避免出现我这样的、隐蔽的可移植性问题.

posted on 2005-07-26 20:47 局部变量 阅读(6052)   

 re: reinterpret_cast和static_cast 2005-07-26 21:16

ms的编译器虚函数表指针都在前面,没有放后面的

也基本可以确定没有编译器会放后面,简而言之,放后面还要知道类型大小才能正确定位虚函数表,会自找麻烦

 re: reinterpret_cast和static_cast 2005-07-27 00:00

vfptr的放置是厂商定义的, 标准并没有规定放在那.
reinterpret_cast是按位转换, 本来就应慎重使用.

 re: reinterpret_cast和static_cast 2005-07-27 08:08

我一般不会把CIoPacket做成虚类

 re: reinterpret_cast和static_cast 2005-07-27 08:56

确实应该用static_cast,但问题其实出在
class CIoPacket : public OVERLAPPED
{
    virtual……
};
这一句,因为OVERLAPPED不是虚基类,而CIoPacket是。

 re: reinterpret_cast和static_cast 2005-07-27 12:57

LPOVERLAPPED pol;
改为
CIoPacket *pol;

说实话,我很奇怪,为什么大家都喜欢这种奇怪的用法,然后不停的去过于追寻编译器的细节内幕。

 re: reinterpret_cast和static_cast 2005-07-27 13:21

首先声明一下只讨论单继承,多继承、虚继承太复杂了

to 问题男:
把vfptr放在后面的编译器也很多(准确的说是第一个有虚函数的类的最后面), 因为放在前面编译器要处理许多类似文中的指针转换(自动增减一定的偏移), 同样也很麻烦.

to SevenCat:
非常想知道你的处理方法, 我要处理很多的异步io操作(有socket也有file),每个操作完成后的处理又各不相同(甚至到项目后期还会有更多不同的操作),我觉得我现在的方案是比较好的,但也一直在寻找更好的

to 周星星:
没太看明白你的意思

to 清风雨:
我觉得这并不是什么奇怪的用法,这只是一个从基类指针到派生类指针的转换,很正常。虽然更正常的是从派生类到基类的转换,但一个完整的项目不可能总工作在理想状态下,必要时用一些“奇计淫巧”会很管用。
而且我觉得不了解编译器的细节内幕, 根本就不可能写出好的程序

 re: reinterpret_cast和static_cast 2005-07-27 15:38

我来给你解释 周星星 的话。
就是OVERLAPPED里面没有虚函数表,所以用虚函数访问时不对了。

我的意思是,如果你直接用CIoPacket *了,也不会出问题。为什么要你直接用CIoPacket *而不是LPOVERLAPPED呢?因为你用到的就不是LPOVERLAPPED的(从你的转型可知),所以也就不需要转型了。个人觉得:不用的就确切的不要用(这里就是是OVERLAPPED而不是CIoPacket的对象实例)。

 re: reinterpret_cast和static_cast 2005-07-27 18:23

各个人的设计都不一样
BOOL GetQueuedCompletionStatus(
HANDLE CompletionPort,
LPDWORD lpNumberOfBytes,
PULONG_PTR lpCompletionKey,
LPOVERLAPPED* lpOverlapped,
DWORD dwMilliseconds
);
这里面还有个lpCompletionKey,这里面也可以放一个CHandler指针。
lpOverlapped是per io的,CHandler是per handle的东东。
在WRITE的发送成功后,基本不必考虑处理,
在READ的读接收的时候,这个PACKET取回的数值也基本无意义,因为TCP本身就是沾包的。
还有人的设计(好像包括APACHE)是这样的:
class AsyncResult : public OVERLAPPED
{
直接放在这个东东里面。
private:
CHandler *_handler;
}
反正怎么做都行,运用之妙,存于一心。

 re: reinterpret_cast和static_cast 2005-07-27 18:24

to清风雨:
这里的确是CIoPacket的实例。只不过偏移量错了 

 re: reinterpret_cast和static_cast 2005-07-27 19:39

to清风雨:
这一点你就冤枉我了,不是我不想直接用CIoPacket*, 而是我不能用。你可能不了解windows的异步io,可以启动异步io的api(如WSARecv, ReadFile等)都要求一个LPOVERLAPPED类型的参数,所以我必须给它。

to SevenCat:
你的设计对我可能不太适合,因为对我来说,同样是从网上接收数据,但接收后处理却可能不一样,我的设计是每种处理定义一个CIoPacket的子类,然后调它的OnIoComplete时自然就能进行正确的处理了。

 re: reinterpret_cast和static_cast 2005-07-27 20:05

你接收数据并不能保证每次的包接收都是正好你想接收包的长度,他会粘包的,你还要折包才行。

 re: reinterpret_cast和static_cast 2005-07-28 08:31

这个对我应该不是问题,因为我能保证发送数据的那一方每次只发送一包数据, 然后就等待我的应答。而每包数据的最前面都有这个数据包的长度,我只要处理被tcp拆包的情况就行了

 re: reinterpret_cast和static_cast 2005-07-28 09:09

CIoPacket* 就是 LPOVERLAPPED

比如:
fun( base *obj );
可以fun( &derived );调用
或者可以在调用时转型。

 re: reinterpret_cast和static_cast 2005-07-28 09:37

to 清风雨:
是呀,你说的没错,所以我在(0)处用的就是pPkt。
但这样做实际上是把转型的任务交给编译器了,那一句相当于:
::WSARecv( sck, &wsabuf, 1, &dwBytes, &dwFlags, static_cast(pPkt), NULL ); 
而就是这个static_cast把pPkt加上了4字节的偏移,导致我在(1)和(2)处必须再用static_cast减去4字节的偏移.
我原来的错误就在于一边用的是static_cast另一边用的是reinterpret_cast,如果vfptr放在后面,这两个cast就是等价的,而vfptr放在前面,它们就不等价。但vfptr放在哪c++标准并没有规定,所以我说原来的错误实际上可以归到可移植性问题上去。

 re: reinterpret_cast和static_cast 2005-08-12 10:11

我不知道你为什么就一定要用reinterpret_cast这个玩意?你就不能不用它吗。

 re: reinterpret_cast和static_cast 2006-01-25 11:23

是不是用dynamic_cast进行转换更好

 to 只说实话 2006-01-25 21:11

你能告诉我怎么不用吗,如果我想把程序写得更规范的话?
实际的程序中不用它是不可能的,而他相比c样式的强制类型转换是有优势的,所以c++才会引入四个类型转换符,并建议程序员不要使用c样式的类型转换

 to 111 2006-01-25 21:14

dynamic_cast效率太低,而且这里没必要,因为我明确的知道我正在转换的基类指针是指向一个派生类

 re: reinterpret_cast和static_cast 2006-07-04 17:15

可以不用reinterpret_cast吗?
为什么编一个一模一样的程序,一个没用reinterpret_cast就报错,一个却可以通过?是不是可以通过什么设置忽略这个错误呢?

 re: reinterpret_cast和static_cast 2007-08-09 10:43

也学了很长一段时间的C++了,但很用使用这个类型转换,一般是使用C样式的,现在看来学问多多啊,多谢各位的言论了。

 re: reinterpret_cast和static_cast 2007-09-20 21:26

vc 6 vfptr是放在前面的.

 re: reinterpret_cast和static_cast 2007-09-20 21:40

我知道的所有的编译器都是放在前面.


--------------------next---------------------

阅读(547) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~