昨天施有个问题问我,就是一个分配了3个WORD和2个DWORD的结构体,计算长度是不是14而是16,我研究了一下,找到原因所在,花了点时间把分析写成邮件发送给他,后来一想,既然已经写出来了,何不发到我的优快云 的blog上?于是就有了这篇拙文
WORD的字长是2,DWORD的字长是4,3个WORD和2个DWORD总共应该是14字节,但是在众多编译器上(gcc,bcc32和cl )均是16字节,于是写了个测试代码来简单分析一下。
我的测试代码如下:
#include <iostream>
#include <windows.h>
using namespace std;
struct STR1
{
WORD x;
WORD c;
WORD v;
// WORD z; //暂时先屏蔽此代码
DWORD b;
DWORD n;
};
int main()
{
STR1 s1;
s1.x = 1; //1语句
s1.c = 2; //2语句
s1.v = 3; //3语句
s1.b = 4; //4语句
s1.n = 5; //5语句
// s1.z = 6; //6语句 //也先暂时屏蔽
cout << sizeof(s1) << endl; //7语句
cin.get();
}
编译执行后,显示STR1的尺寸是16,ok,保存生成的二进制文件,用IDA反编译此文件,分析结果如下:
.text:00401294 ; int __cdecl main(int argc,const char **argv,const char *envp) ;这个就是主函数了
.text:00401294 _main proc near ; DATA XREF: .data:0043804Co
.text:00401294
.text:00401294 var_10 = word ptr -10h ;开始在栈中分配空间,注意这里开始分配结构体空间
.text:00401294 var_E = word ptr -0Eh
.text:00401294 var_C = word ptr -0Ch
.text:00401294 var_8 = dword ptr -8
.text:00401294 var_4 = dword ptr -4
.text:00401294 argc = dword ptr 8
.text:00401294 argv = dword ptr 0Ch
.text:00401294 envp = dword ptr 10h
.text:00401294
.text:00401294 push ebp
.text:00401295 mov ebp, esp ;开辟新的栈空间,调用函数,保存现场
.text:00401297 add esp, 0FFFFFFF0h ;需要用的内存大小
.text:0040129A mov [ebp+var_10], 1 ;对应C源码中的1语句
.text:004012A0 mov [ebp+var_E], 2 ;后面类推
.text:004012A6 mov [ebp+var_C], 3
.text:004012AC mov [ebp+var_8], 4
.text:004012B3 mov [ebp+var_4], 5
.text:004012BA push offset sub_401308 ;第7语句
.text:004012BF push 10h
.text:004012C1 push offset unk_445AA0
.text:004012C6 call @$beql$qrx5_GUIDt1 ; operator==(_GUID &,_GUID &)
.text:004012CB add esp, 8
.text:004012CE push eax
.text:004012CF call sub_4012FC
.text:004012D4 add esp, 8
.text:004012D7 push offset unk_445A18
.text:004012DC call sub_401334
.text:004012E1 pop ecx
.text:004012E2 xor eax, eax
.text:004012E4 mov esp, ebp
.text:004012E6 pop ebp
.text:004012E7 retn
.text:004012E7 _main endp
这里要注意的是0040129A 开始执行的代码,注意分到栈空间是-10, -e, -c, -8, -4, 对应的结构体的数据类型并不完全对应,各相应分配为2,2, 4, 4, 4 字节数,共计16字节,假设分配时栈空间从0012FF7C 开始,则执行1语句后,栈结果如下:
0012FF7C > 00440001 <<结构体的开始,分配了2字节
0012FF80 > 00000001
0012FF84 > 00000100
0012FF88 > 00000001
0012FF8C 0012FFB8
执行2语句后
0012FF7C > 00020001 <<结构体分配两字节,2被分配
0012FF80 > 00000001
0012FF84 > 00000100
0012FF88 > 00000001
0012FF8C 0012FFB8
执行3语句后
0012FF7C > 00020001
0012FF80 > 00000003 <<又被分配两字节,3被分配,注意下步操作将分配DWORD类型数据
0012FF84 > 00000100
0012FF88 > 00000001
0012FF8C 0012FFB8
执行4语句后
0012FF7C > 00020001
0012FF80 > 00000003
0012FF84 > 00000004 <<4被分配到这里了,而不是0012FF82,因为这里分配的是DWORD类型
0012FF88 > 00000001
0012FF8C 0012FFB8
执行5语句后
0012FF7C > 00020001
0012FF80 > 00000003
0012FF84 > 00000004
0012FF88 > 00000005 <<5被分配
0012FF8C 0012FFB8
这里当执行第三步命令后,由于下个指令是分配DWORD,也就是4字节立即数的时候,将会分配到下一个正好被32位数整除的内存地址中(这样CPU计算寻址会更方便,因为现在的CPU都是32位字长指令的,但也会对我们造成一些困扰),也就是说 0012FF80 地址的后半部分并没有使用,但却占用了空间,虽然是一些浪费,但是intel认为这样换来的计算寻址会更迅速。
再多一步:
这回将源码中结构体中屏蔽的代码解除屏蔽(也将main函数中的屏蔽解除掉),然后在编译运行,这时结果仍然是16,再观察
执行6语句后
0012FF7C > 00020001
0012FF80 > 00060003 <<6被分配在这里
0012FF84 > 00000004
0012FF88 > 00000005
0012FF8C 0012FFB8