【Delphi】32位源码编译64位程序时的字节对齐问题

        delphi XE2新增对64位程序的支持,从框架到编译器都相当成熟,不过由于原有代码都是针对32位,在重新编译成64位,或者在编写64位程序时,由于32位代码的编写习惯,有时会出现一些莫名奇妙的问题。

        如最近在编写password filter的时候,发现同样的源码编译到32位可以正常工作,但编译成64位就出现的异常,最后发现是在实现其中PasswordFilter和PasswordChangeNotify时其参数为UNICODE_STRING的问题, 在访问Buffer字段时内存非法访问异常,该结构定义如下:

_UNICODE_STRING=PACKED RECORD
     Length,
     MaximumLength:USHORT;
     Buffer:PWSTR;
END;

在32位系统中,CPU寻址按4字节对齐,该结构内存结构 PACKED后其实与没有PACKED是一样的,

首先认识到寻址的对齐目的,是为了避免CPU访问数据时由于数据跨边界而需要2次寻址的问题,而32位单次寻址大小是4字节,

 Length加上MaximumLength 的大小只有4字节,刚好可以将该2字段存放同一边界内,

在访问Length和MaximumLength时实际都分别只需要寻址一次, Buffer也是寻址一次,

也就是说这里即使使用PACKED ,CPU访问所有字段的寻址次数也没有变化,因此PACKED后与没有PACKED大小是一样的。

 

但是在64位系统,这里就出现差别了,因为64位的CPU寻址是按照8字节对齐的,由于Length加上MaximumLength 的大小只有4字节,可以存放在同一边界内,这时该边界内仍然剩余4字节, 结构使用PACKED后,将导致 Buffer紧跟着存放在剩余的4字节中,而Buffer为8字节的指针,因此还有4字节需要存放到下一个边界,也就是 Buffer 数据存跨越两个寻址边界,结果CPU要取得 Buffer数据就需要寻址2次。

微软的64位系统通常不会出现这种存在跨边界字段的结构体,因为会增加寻址次数导致速度降低,因此他们在64位系统中对该结构进行修补,如下:

_UNICODE_STRING=PACKED RECORD
      Length,
      MaximumLength:USHORT;
      {$IFDEF CPUX64}
       //64位机需要8字节对齐, 但packed会影响该对齐,导致Buffer指向位置错位
       //这里添加nReserve1,nReserve2来调整
       nReserve1: USHORT;
       nReserve2: USHORT;
      {$ENDIF}
      Buffer:PWSTR;
END;

这样一来,Buffer会被“挤到”下一边界,CPU只需寻址一次就够了,与32位的结构寻址次数相等,经过该结构体的修改,再编译成64位的

password filter就可以正常工作了。

以上可以看出,32位的源码要编译出正确的64位程序,通常需要修改各种系统结构体的定义。

 

posted on 2011-11-17 10:06  峋山隐修会  阅读(1182)  评论(0编辑  收藏  举报

导航