参考资料:
   http://data.gameres.com/message.asp?TopicID=13636

   我们在分析程序时,经常需要分析一些参数,这些参数不乏有结构类型数据,下面先看一个结构定义(c++/vc++):
typedef  struct
{
  DWORD  dwParam4; //4 bytes
           char      cbParam1; //1 byte
  long  nParam4;  //4 bytes
           double    fParam8;  //8 bytes
}TestStruc,*pTestStruc;

使用例子:
int Test(void)
{
  TestStruc strucTest;
  strucTest.dwParam4 = 41;
  strucTest.cbParam1 = 0x40;
  strucTest.nParam4 = 42;
  strucTest.fParam8 = 100.f;
  //
  char* p = (char*)&strucTest;
  long nTest1 = strucTest.nParam4;
  long nTest2 = *(long*)(p+sizeof(DWORD)+sizeof(char));

  //请问:下面这句显示OK还是Err
  if( nTest1 == nTest2 )
  {
     ::MessageBox(NULL,"OK","OK",MB_OK);
  }
  else
  {
     ::MessageBox(NULL,"Err","Err",MB_OK);
  }
}
   一定显示OK吗?您可以自己在vc++中编译一下看看就知道了,会显示Err。
   
   按照常理,我们用IDA在内存中应该看到如下数据:
   &strucTest地址:(00A5B930):
   00A5B930   41 00 00 00 40 42 00 00 
   00A5B938   00 00 00 00 00 00 00 59 40

   可是,我们看到的却是:
   &strucTest地址:(00A5B930):
   00A5B930   41 00 00 00 40 00 00 00
   00A5B938   42 00 00 00 00 00 00 00
   00A5B940   00 00 00 00 00 00 59 40

    为什么呢?
    请看(以下内容节选自《Intel Architecture 32 Manual》)
    字,双字,和四字在自然边界上不需要在内存中对齐。(对字,双字,和四字来说,自然边界分别是偶数地址,可以被4整除的地址,和可以被8整除的地址。)
    无论如何,为了提高程序的性能,数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;然而,对齐的内存访问仅需要一次访问。
    一个字或双字操作数跨越了4字节边界,或者一个四字操作数跨越了8字节边界,被认为是未对齐的,从而需要两次总线周期来访问内存。一个字起始地址是奇数但却没有跨越字边界被认为是对齐的,能够在一个总线周期中被访问。
    某些操作双四字的指令需要内存操作数在自然边界上对齐。如果操作数没有对齐,这些指令将会产生一个通用保护异常(#GP)。双四字的自然边界是能够被16整除的地址。其他的操作双四字的指令允许未对齐的访问(不会产生通用保护异常),然而,需要额外的内存总线周期来访问内存中未对齐的数据。

    编译器对内存对齐的处理
    缺省情况下,c/c++编译器默认将结构、栈中的成员数据进行内存对齐。因此,上面的程序输出就变成了:
c1 00000000, s 00000002, c2 00000004, i 00000008。
编译器将未对齐的成员向后移,将每一个都成员对齐到自然边界上,从而也导致了整个结构的尺寸变大。尽管会牺牲一点空间(成员之间有空洞),但提高了性能。


   所以就出现了上面的情况,当我们用IDA等动态调试分析变量时候,要充分考虑这些情况,才能够正确分析。特别是在动态分析一些函数入口参数的时候,考虑不到这些情况,很容易陷入误区,被本来可以很容易理解的一些变量弄昏了头。

   那么,是不是所有的结构都这样呢?当然不是,在编译的时候,可以通过编译选项改变。

    使用c/c++中的对齐选项
    vc6中的编译选项有 /Zp[1|2|4|8|16] ,/Zp1表示以1字节边界对齐,相应的/Zpn表示以n字节边界对齐。n字节边界对齐的意思是说,一个成员的地址必须安排在成员的尺寸的整数倍地址上或者是n的整数倍地址上,取它们中的最小值。 实际上,1字节边界对齐也就表示了结构成员之间没有空洞。
    /Zpn选项是应用于整个工程的,影响所有的参与编译的结构。
    要使用这个选项,可以在vc6中打开工程属性页,c/c++页,选择Code Generation分类,在Struct member alignment可以选择。

    要专门针对某些结构定义使用对齐选项,可以使用#pragma pack编译指令。指令语法如下:
#pragma pack( [ show ] | [ push | pop ] [, identifier ] , n  )

意义和/Zpn选项相同,例如,修改前面定义为:
#pragma pack(1) //配合使用/ZP编译选项,将自定义结构锁定在字节边界
typedef  struct
{
  DWORD  dwParam4; //4 bytes
           char      cbParam1; //1 byte
  long  nParam4;  //4 bytes
           double    fParam8;  //8 bytes
}TestStruc,*pTestStruc

这样,在编译一下上面的程序,就显示OK了。
                                              Spring.W