本机尺寸与结构体对齐
本机尺寸
如果本机是32位的,那么对32位的支持是最好的
在很多的书上都能看到类似这样的代码
#include<stdio.h>
void Function(char x,char y)
{
char c=x+y;
}
void main()
{
char a=1;
char b=2;
Function(a,b);
}
我们通过反汇编会发现
1: #include<stdio.h> 2: void Function(char x,char y) 3: { 00401020 push ebp 00401021 mov ebp,esp 00401023 sub esp,44h 00401026 push ebx 00401027 push esi 00401028 push edi 00401029 lea edi,[ebp-44h] 0040102C mov ecx,11h 00401031 mov eax,0CCCCCCCCh 00401036 rep stos dword ptr [edi] 4: char c=x+y; 00401038 movsx eax,byte ptr [ebp+8] 0040103C movsx ecx,byte ptr [ebp+0Ch] 00401040 add eax,ecx 00401042 mov byte ptr [ebp-4],al 5: } 00401045 pop edi 00401046 pop esi 00401047 pop ebx 00401048 mov esp,ebp 0040104A pop ebp 0040104B ret --- No source file ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 0040104C int 3 0040104D int 3 0040104E int 3 0040104F int 3 --- C:\Windows\System32\a.cpp ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 6: void main() 7: { 00401050 push ebp 00401051 mov ebp,esp 00401053 sub esp,48h 00401056 push ebx 00401057 push esi 00401058 push edi 00401059 lea edi,[ebp-48h] 0040105C mov ecx,12h 00401061 mov eax,0CCCCCCCCh 00401066 rep stos dword ptr [edi] 8: char a=1; 00401068 mov byte ptr [ebp-4],1 9: char b=2; 0040106C mov byte ptr [ebp-8],2 10: Function(a,b); 00401070 mov al,byte ptr [ebp-8] 00401073 pusheax 00401074 mov cl,byte ptr [ebp-4] 00401077 pushecx 00401078 call @ILT+0(Function) (00401005) 0040107D add esp,8 11: } 00401080 pop edi 00401081 pop esi 00401082 pop ebx 00401083 add esp,48h 00401086 cmp ebp,esp 00401088 call __chkesp (004010a0) 0040108D mov esp,ebp 0040108F pop ebp 00401090 ret
蓝色部分是传递进去的参数
然后我们发现,尽管定义的是char类型,压栈的却是eax而并不是al。
在32位的系统中,系统默认最合适的数据类型,就是32个bit,即4字节。同理,64位的系统就是8字节。
也就是说,如果使用小于4个字节的局部变量来进行参数传递,vc编译器仍然会按照4个字节来进行传递,但是多余的部分并不会使用。
从上面我们能看到,当创建一个小于4字节局部变量时,编译器为我们分配的内存空间为0x44。
当我们创建两个局部变量时
1: #include<stdio.h> 2: void Function(char x,char y) 3: { 00401020 push ebp 00401021 mov ebp,esp 00401023 sub esp,44h 00401026 push ebx 00401027 push esi 00401028 push edi 00401029 lea edi,[ebp-44h] 0040102C mov ecx,11h 00401031 mov eax,0CCCCCCCCh 00401036 rep stos dword ptr [edi] 4: char c=x+y; 00401038 movsx eax,byte ptr [ebp+8] 0040103C movsx ecx,byte ptr [ebp+0Ch] 00401040 add eax,ecx 00401042 mov byte ptr [ebp-4],al 5: char d; 6: } 00401045 pop edi 00401046 pop esi 00401047 pop ebx 00401048 mov esp,ebp 0040104A pop ebp 0040104B ret --- No source file ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 0040104C int 3 0040104D int 3 0040104E int 3 0040104F int 3 --- C:\Windows\System32\a.cpp ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 7: void main() 8: { 00401050 push ebp 00401051 mov ebp,esp 00401053 sub esp,48h 00401056 push ebx 00401057 push esi 00401058 push edi 00401059 lea edi,[ebp-48h] 0040105C mov ecx,12h 00401061 mov eax,0CCCCCCCCh 00401066 rep stos dword ptr [edi] 9: char a=1; 00401068 mov byte ptr [ebp-4],1 10: char b=2; 0040106C mov byte ptr [ebp-8],2 11: Function(a,b); 00401070 mov al,byte ptr [ebp-8] 00401073 push eax 00401074 mov cl,byte ptr [ebp-4] 00401077 push ecx 00401078 call @ILT+0(Function) (00401005) 0040107D add esp,8 12: } 00401080 pop edi 00401081 pop esi 00401082 pop ebx 00401083 add esp,48h 00401086 cmp ebp,esp 00401088 call __chkesp (004010a0) 0040108D mov esp,ebp 0040108F pop ebp 00401090 ret
缓冲区变为0x48了
再尝试创建一个大于4字节的数
1: #include<stdio.h> 2: void Function(char x,char y) 3: { 00401020 push ebp 00401021 mov ebp,esp 00401023 sub esp,4Ch 00401026 push ebx 00401027 push esi 00401028 push edi 00401029 lea edi,[ebp-4Ch] 0040102C mov ecx,13h 00401031 mov eax,0CCCCCCCCh 00401036 rep stos dword ptr [edi] 4: char c=x+y; 00401038 movsx eax,byte ptr [ebp+8] 0040103C movsx ecx,byte ptr [ebp+0Ch] 00401040 add eax,ecx 00401042 mov byte ptr [ebp-4],al 5: __int64 d; 6: } 00401045 pop edi 00401046 pop esi 00401047 pop ebx 00401048 mov esp,ebp 0040104A pop ebp 0040104B ret --- No source file ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 0040104C int 3 0040104D int 3 0040104E int 3 0040104F int 3 --- C:\Windows\System32\a.cpp ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 7: void main() 8: { 00401050 push ebp 00401051 mov ebp,esp 00401053 sub esp,48h 00401056 push ebx 00401057 push esi 00401058 push edi 00401059 lea edi,[ebp-48h] 0040105C mov ecx,12h 00401061 mov eax,0CCCCCCCCh 00401066 rep stos dword ptr [edi] 9: char a=1; 00401068 mov byte ptr [ebp-4],1 10: char b=2; 0040106C mov byte ptr [ebp-8],2 11: Function(a,b); 00401070 mov al,byte ptr [ebp-8] 00401073 push eax 00401074 mov cl,byte ptr [ebp-4] 00401077 push ecx 00401078 call @ILT+0(Function) (00401005) 0040107D add esp,8 12: } 00401080 pop edi 00401081 pop esi 00401082 pop ebx 00401083 add esp,48h 00401086 cmp ebp,esp 00401088 call __chkesp (004010a0) 0040108D mov esp,ebp 0040108F pop ebp 00401090 ret
这里就变成0x4c,因为__int64是8位的,所以就是0x48+4=0x4c了。
总结:
0x00.在32位系统上创建小于32位的局部变量是不合适的,在空间分配时仍然会按照32位来进行分配。但多余部分并不会使用。所以尽量避免使用小于32位的局部变量。
0x01.在vc6.0中调用函数时,每创建一个小于32位的局部变量,就会分配4个字节的空间。如果创建大于32位的局部变量,就会分配该局部变量实际的空间。
总的来说就是一句话,在32位的windows系统,使用vc6编程时尽量不要创建小于4个字节的局部变量,整数类型的参数,一律int类型。
结构体对齐
在计算机中,结构体通常是字节对齐的。这样在数据的存储方面能达到最好的效率。空间和时间的取舍问题~~~
例子:
1:
#include<stdio.h> struct test { char a; int b; char c; }; int main() { printf("%d",sizeof(test)); return 0; }
2.
#include<stdio.h> struct test { int b; char a; char c; }; int main() { printf("%d",sizeof(test)); return 0; }
此时,前者打印出来的大小是12,而后者打印的结果却是8。
如果想控制这个对齐字节可以通过加上两行代码。
1 #pragma pack(1) 2 struct Test 3 { 4 int a; 5 __int64 b; 6 char c; 7 } 8 #pragma pack()
此时对齐的字节就是1了。
那么当不写#pragma时,又是对齐的字节又是多少呢?
这是和编译器有关系。比如我使用的是vc6.0,可以在这里看到:
不建议修改这里参数。
结构体对齐又可以叫字节对齐。大致遵循以下四个原则。
原则1:数据成员对齐规则,结构的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小的整数倍开始(比如int在32位机为4字节,则要从4的整数倍开始存储)
原则2:结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足要补齐。
原则3:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储。(struct a里存有struct b,b里有char,int,double 等元素,那b应该从8的整数倍开始存储)
原则4:对齐参数如果比结构体成员的sizeof值小,该成员的偏移量应该以此值为准。也就是说,结构体成员的偏移量应该取二者的最小值
其实很好理解,随便一个例子。
struct test { int b; char a; char c; };
这个就相当于这样。所以这个是8。
再来一个,同样还是上文中提到的。
struct test { char a; int b; char c; };
总的来说。一个结构体中,所占空间最大的类型,和对齐字节比较。取较小的,记为x。然后该结构体第一个成员记为s1,第二个记为s2。。。。。
然后x-s1与s2比较,小于的话重新加一个x,另起一行,而第一个成员的后面就空着浪费掉了,被填充上数据cc,即int3。大于的话则变为x-s1-s2与s3比较。(自己理解的)
所以会有,结构体大小一定是最大成员大小的整数倍。
那么加入一个结构体中有另一个结构体该如何计算。
#include<stdio.h> struct test1 { int a; char b; }; struct test2 { test1 a; int b; }; int main() { printf("%d",sizeof(test2)); return 0; }
结果仍然是12。
这是因为test2实际就相当于这样一个结构体。(仍然是自己的理解,不过感觉是对的~~~错误欢迎指出)
struct test2 { int a; char b; int c; };