滴水-day13-结构体-字节对齐
结构体
定义结构体
struct name
{
int a;
char b;
short c;
char s[20];
}
结构体与int,short,char....是同级的,只是这些内置的数据类型无法满足需求。
结构体中可以存储除自身外的任何数据类型。
结构体的使用
name AA; // 声明一个 name 类型的数据
AA.a = 10; // 给AA中的a属性赋值
AA.b = 20;
AA.c = 30;
AA.s[0] = 1; // 给AA中s属性的第一个元素赋值
结构体传参数
// 结构体定义
struct coord
{
int x;
int y;
int h;
};
void testStructParr(coord cd)
{
}
// 传递参数
coord cd;
cd.x = 10;
cd.y = 20;
cd.h = 30;
testStructParr(cd);
002E1A7F mov dword ptr [ebp-10h],0Ah
002E1A86 mov dword ptr [ebp-0Ch],14h
002E1A8D mov dword ptr [ebp-8],1Eh
002E1A94 sub esp,0Ch
002E1A97 mov eax,esp
002E1A99 mov ecx,dword ptr [ebp-10h]
002E1A9C mov dword ptr [eax],ecx
002E1A9E mov edx,dword ptr [ebp-0Ch]
002E1AA1 mov dword ptr [eax+4],edx
002E1AA4 mov ecx,dword ptr [ebp-8]
002E1AA7 mov dword ptr [eax+8],ecx
002E1AAA call 002E13C5
结构体传递参数,当我向coord结构体传递三个参数的时候反汇编是这样的。
首先在main函数中为这个三个参数分配了空间,可以看作是局部变量。
esp减去了c个字节,也就是将esp向上提升了c个字节,这些字节刚好够放下参数。
把esp赋值给了eax,也就是新的栈顶
使用mov依次对eax内存地址进行赋值,每赋值一次eax就+4直到参数传递完毕。
这样就完成了参数的传递,虽然没有push指令。
结构体返回值
coord testScructRet()
{
coord cd;
cd.x = 10;
cd.y = 20;
cd.h = 30;
return cd;
}
coord cd2 = testScructRet();
005E5432 lea eax,[ebp-10Ch]
005E5438 push eax
005E5439 call testScructRet (05E13CAh)
005E543E add esp,4
005E5441 mov ecx,dword ptr [eax]
005E5443 mov dword ptr [ebp-0F8h],ecx
005E5449 mov edx,dword ptr [eax+4]
005E544C mov dword ptr [ebp-0F4h],edx
005E5452 mov eax,dword ptr [eax+8]
005E5455 mov dword ptr [ebp-0F0h],eax
005E545B mov ecx,dword ptr [ebp-0F8h]
005E5461 mov dword ptr [cd2],ecx
005E5464 mov edx,dword ptr [ebp-0F4h]
005E546A mov dword ptr [ebp-20h],edx
005E546D mov eax,dword ptr [ebp-0F0h]
005E5473 mov dword ptr [ebp-1Ch],eax
005E5432 lea eax,[ebp-10Ch]
005E5438 push eax
首先在main函数中通过ebp-10C去地址编号放到eax中,在之前的学习中可以知道ebp-??是存放局部变量的地方。把eax压入栈中,这里很明显是在准备局部变量空间用来接收数据。10C是局部变量最大存储位置
testScructRet函数
005E41C5 mov dword ptr [ebp-10h],0Ah
005E41CC mov dword ptr [ebp-0Ch],14h
005E41D3 mov dword ptr [ebp-8],1Eh
005E41DA mov eax,dword ptr [ebp+8]
005E41DD mov ecx,dword ptr [ebp-10h]
005E41E0 mov dword ptr [eax],ecx
005E41E2 mov edx,dword ptr [ebp-0Ch]
005E41E5 mov dword ptr [eax+4],edx
005E41E8 mov ecx,dword ptr [ebp-8]
005E41EB mov dword ptr [eax+8],ecx
005E41EE mov eax,dword ptr [ebp+8]
三个局部变量0AH,14H,1EH。取ebp+8放到eax,这里的ebp+8其实就是刚才push的eax,也就是main函数局部变量最大空间的地址。
接下来就是通过大量的mov 把testScructRet的 局部变量放到寄存器中,再通过寄存器放到eax中。这样就完成了的了对main局部变量的赋值,也就相当于返回值了。
字节对齐,结构体数组
Sizeof关键字
sizeof可以获取数据类型的大小,可以通过数据类型名称或者是变量名称获取。
sizeof是关键字不是函数,只是用起来像一个函数。
字节对齐
在结构体中分配空间的依据是跟进字节对齐的大小进行分配的
默认字节对齐是8字节,也就是说以8字节对齐为标准
可以使用:#pragma pack(n)
结构体
\#pragma pack
n可以是1,2,4,8.中的任意一个
结构体的顺序不同,分配空间大小也不同。
原则一:数据成员对齐规则:结构的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小的整数倍开始(比如int在32位机为4字节,则要从4的整数倍地址开始存储).
原则二:结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐。
原则三:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储。 (struct a里存有struct b,b里有char,int,double等元素,那b应该从8的整数倍开始存储.)
原则四:对齐参数如果比结构体成员的sizeof值小,该成员的偏移量应该以此值为准.
也就是说,结构体成员的偏移量应该取二者的最小值.
例如:
struct aa
{
int a;
char b;
short c;
}
默认是8字节:
a a a a
b 0 c c
1字节:
a a a a
b c c
2字节:
a a a a
b 0 c c
4字节:
a a a a
b 0 c c
Typedef
typedf关键字是对一种数据类型取一个新的名字,包括内置的数据类型。
typedef unsigned int DWORD
typedef unsigned char BYTE
typedef unsigned short WORD
typedef struct student
{
int a;
int b;
int c;
}stu;
字符数组赋值
char arr[10]={0};
arr[0]='a';
arr[1]='b';
arr[2]='c';
arr[3]='\0';
通过内置函数可以快速进行赋值
strcpy(arr,"abc");
结构体数组
就是定义一个结构体类型的数组罢了,每个下标表示一个结构,通常用来存储大量信息且有重复的数据。
typedef struct coordinate // 8
{
int x;
int y;
}coord;
typedef struct Monster // 44
{
int id;
int hp;
int gongji;
int yidong;
char monName[20];
coord cd;
}mons;
// 2、声明一个Monster类型的数组,长度为10.
mons monsInfo[10];
练习
typedef struct coordinate // 8
{
int x; // 4
int y; // 4
}coord;
typedef struct Monster // 44 20+8+16=44
{
int id; // 4
int hp; // 4
int gongji; // 4
int yidong; // 4
char monName[20]; 16 + 44440000 = 44444444 = 16+8
coord cd; // 8-4 = 4
}mons;
// 2、声明一个Monster类型的数组,长度为10.
mons monsInfo[10];