结构体原理(内存结构)
date: April 30, 2022 summary: 重点说其内存结构、对齐格式 type: Post
结构体的介绍及定义
结构体是一种复合结构类型,可以将其视为类和对象的前身,一般定义在头文件中
结构体的功能在于用不同的数据集合描述一个实体
但是要描述一个东西,数据是说了不算的,数据大多数情况下无法明确的区分两种东西,比如我们**想要描述一个人是小偷,仅仅描述这个人的身高、体重、穿着...这些数据是没用的,所以除了数据还要加上行为才能确实的区分一个东西,**对于上面的例子,我们要加上这个人有偷东西的行为后,才可以确认他是小偷
这也就是类(数据+行为)和结构体(数据)的区别
⚠️ 当然,结构体中也可以加入函数指针,这里不做讨论结构体的设计难题
使用结构体的难点在于结构体的设计:一旦项目中的结构体设计出现问题,就是骨架的问题,会导致整个项目出问题,整个都需要推倒重来
浅说结构体的设计
现在有如下结构体:目的用于存储公民个人基本信息
请问:该定义是否合理?
struct tagPerson
{
char szName[5]; //姓名
int nAge; //年龄
bool cGender; //性别
char *szPhoneNumber; //电话号码
float fHeight; //身高
double dblWeight; //体重
};
答:不合理!
-
不应该定义
nAge年龄:年龄是会变化的,人多时总要刷新该数据QQ曾出现过这样的问题,就是这样定义的,造成的问题在于,若几年没有登录, 会导致年龄不变
后来为了解决该问题,该为出生日期,直接计算即可
-
国际中性别有四种,无法使用
🤨 M Male 男性——F Female 女性——U Unknow 未知——O Other 其他bool型 -
身高、体重,身高、体重与本人没关系与检查测量有关系,不应该加入结构体
身高、体重也会随时变化(若想绘制一段时间内的身高、体重变化该结构体实现不了)
📌 该结构体为个人基本信息,若想记录身高、体重等测量信息,需要再建立一个结构体 用于专门存储这类信息
语法与原理
基本语法不做过多介绍,主要介绍原理部分:内存结构等
struct tagPerson
{
char szName[5];
int nAge;
char cGender;
char *szPhoneNumber;
float fHeight;
double dblWeight;
};
struct tagTest
{
};
int main(int argc,char* argv[],char* envp[])
{
struct tagPerson per = {
"Jack",
18,
'M',
"110",
175.6f,
60.5
};
struct tagTest test;
}
Some基本语法
接下来给出的示例,均写在上述代码的
main()函数中
空结构体
struct tagTest test;
printf("%d\r\n",sizeof(test));
由上述代码可知,tagTest是一个空结构体
此时sizeof(test) = 1 ,空结构体的大小为1(实际上对齐后大小为4)
结构体的访问
主要两种方式:取成员
.运算 和 结构体指针—>运算
1:取成员 . 运算
printf("%d\r\n",per.nAge);
. 的优先级仅次于()
2:结构体指针 —> 运算
struct tagPerson *pPer = &per; //结构体指针
printf("%d\r\n",pPer->nAge);
= (*pPer).nAge
= (&per)->nAge
不推荐后两种写法
相同的结构体之间的赋值
相同结构体之间可直接赋值
-
相同结构体直接赋值
struct tagPerson per2 = per -
使用
memcpy()进行结构体的复制🥳插播一条:有关
memcpy()的介绍:(主要从与strcpy()对比中得出)- 复制的内容不同。
strcpy()只能复制字符串,而memcpy()可以复制任意内容,例如字符数组、整型、结构体、类等 - 复制的方法不同。
strcpy()不需要指定长度,它遇到被复制字符的串结束符"\0"才结束,所以容易溢出。memcpy()则是根据其第3个参数决定复制的长度 - 用途不同。通常在复制字符串时用
strcpy(),而需要复制其他类型数据时则一般用memcpy()
所以上述结构体的复制还可以写为:
memcpy(&per2,&per,sizeof(struct tagPerson)); //将per复制给per2 - 复制的内容不同。
结构体的传参
结构体传参传的就是数据,不是地址,所以若传参为结构体,就会在栈中整个开辟一块=结构体定义大小的空间
原理部分:结构体的内存结构
问题的出现
#include <stdio.h>
struct tagPerson
{
char szName[5];
int nAge;
char cGender;
char *szPhoneNumber;
float fHeight;
double dblWeight;
};
int main(int argc)
{
struct tagPerson per = {
"Jack",
18,
'M',
"110",
175.6f,
60.5
};
printf("%d",sizeof(per));
return 0;
}
按照我们以前的思维,对于内存中存储对齐的这个问题,应当是每个数据分别对齐,算总长度即可,那么对于上述 sizeof(per) 的结果就应该是 8+4+4+4+4+8 = 32
那么按照上面的逻辑,若我们只调换结构体中数据类型的位置,sizeof()后大小应该是不变的,我们将顺序换一下(将dbWeight拿到cGender前面)
struct tagPerson
{
char szName[5];
int nAge;
double dblWeight;
char cGender;
char *szPhoneNumber;
float fHeight;
};
此时奇迹发生了:仅仅换了顺序,导致sizeof()也发生了变化,也就说明了之前我们说的结构体大小的计算方式是不对的
对齐结构
结构体的对齐单位是可以设置的,通过编译选项中的
/Zp进行设置
结构体的对齐要求不是编译器提出的,而是网络通讯提出的
网络通讯中,经常用结构体定义数据包,某些网络协议中双方需要约定对齐值,否则会影响结构体内部布局
🍁 网路中没有约定对齐值的,其对齐值默认为1结构体的对齐值
/Zp {1;2;4;8;16}都可以,默认对齐值为 8(整体和各成员变量都有对齐值)
Project -> Settings -> C/C++ -> Code Generation -> Struct member alignment(结构体对齐单位)
使用默认对齐值时不会显示在编译选项中
若要使用其余值,会在编译选项中添加 /Zp1 /Zp2...
如何计算( ⭐⭐⭐ 吹包儿star)非常重要!!
第一大步骤:
首先我们规定这样几个值:
MemberAlig成员变量__自身的对齐值MemberOffset成员变量__距结构体首地址的字节数(成员偏移量)MemberType成员变量__的数据类型
他们之间的关系必须满足:
MemberAlig = min(/Zp,sizeof(MemberType))MemberOffset % MemberAlig == 0(否则++)
⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️
第二大步骤:
设结构体变量的对齐值为StructAlig
StructAlig = max(sizeof(Member1Type),sizeof(Member2Type)...,sizeof(MemberEndType))
StructAlig = min(Zp,StructAlig)
同时必须满足
StructSize % min(Zp,StructAlig) == 0 (否则++)
举例说明(VC6.0)
/Zp = 8
struct tagPerson
{
char szName[5];
int nAge;
double dblWeight;
char cGender;
char *szPhoneNumber;
float fHeight;
};
的对齐格式:
第一大步骤:(逐行分析)
char szName[5];
-
MemberAlig = min(/Zp,sizeof(char))= min(8,1) = 1 -
MemberOffset % MemberAlig == 00 % 1 = 0szName[5]处于结构体中的第一个元素→MemberOffset=0 -
由于 0 % 1 = 0 满足
MemberOffset % MemberAlig == 0所以无需做额外变化所以szName[5]被装载到Offset = 0的位置
int nAge;
-
MemberAlig = min(/Zp,sizeof(int))= min(8,4) = 4 -
MemberOffset % MemberAlig == 06 % 4 ≠ 0szName[5]的MemberAlig为 1 且MemberOffset=0, 0+1*5 = 5 → nAge目前的的MemberOffset=6 -
由于 6 % 4 ≠ 0 不满足
MemberOffset % MemberAlig == 0所以 ++→7 % 4 ≠ 0 再++→8 % 4 = 0所以
nAge的实际MemberOffset = 8(不是6)所以nAge被装载到Offset = 8的位置
double dblWeight;
-
MemberAlig = min(/Zp,sizeof(double))= min(8,8) = 8 -
MemberOffset % MemberAlig == 012 % 8 ≠ 0nAge的MemberAlig为 4 且MemberOffset=8, 4+8 = 12 → dblWeight目前的MemberOffset=12 -
由于 12 % 8 ≠ 0 不满足
MemberOffset % MemberAlig == 0所以 ++→13 % 8 ≠ 0 再++→14 % 4 ≠ 0 再++→15 % 8 ≠ 0 再++→16 % 8 = 0所以
dblWeight的实际MemberOffset = 16(不是12)所以dblWeight被装载到Offset = 16的位置
char cGender;
-
MemberAlig = min(/Zp,sizeof(char))= min(8,1) = 1 -
MemberOffset % MemberAlig == 024 % 8 = 0dblWeight的MemberAlig为 8 且MemberOffset=16, 16+8 = 24 → cGender目前的MemberOffset=24 -
由于 24 % 8 = 0 满足
MemberOffset % MemberAlig == 0所以无需做额外变化cGender的MemberOffset = 24所以dblWeight被装载到Offset = 24的位置
char *szPhoneNumber;
-
MemberAlig = min(/Zp,sizeof(char *))= min(8,4) = 4 -
MemberOffset % MemberAlig == 025 % 4 ≠ 0cGender的MemberAlig为 1 且MemberOffset=24, 1+24 = 25 → szPhoneNumber目前的MemberOffset=25 -
由于 25 % 4 ≠ 0 不满足
MemberOffset % MemberAlig == 0所以 ++→26 % 4 ≠ 0 再++→27 % 4 ≠ 0 再++→28 % 4 = 0所以
szPhoneNumber的实际MemberOffset = 28(不是25)所以szPhoneNumber被装载到Offset = 28的位置
float fHeight;
-
MemberAlig = min(/Zp,sizeof(float))= min(8,4) = 4 -
MemberOffset % MemberAlig == 032 % 4 = 0szPhoneNumber的MemberAlig为 4 且MemberOffset=28, 4+28 = 32 → fHeight目前的MemberOffset=32 -
由于 32 % 4 = 0 满足
MemberOffset % MemberAlig == 0所以无需做额外变化所以
fHeight的MemberOffset = 32所以fHeight被装载到Offset = 32的位置
第一大步骤end
第二大步骤
设结构体变量的对齐值为StructAlig
StructAlig = max(sizeof(char),sizeof(int),sizeof(double),sizeof(char),sizeof(char),sizeof(float))= max(1,4,8,1,1,4) = 8StructAlig = min(Zp,StructAlig)= min(8,8) = 8StructSize % min(Zp,StructAlig) == 0= (32 + 4) % 8 ≠ 0 再++ → → 40 % 8 = 0😵💫😵💫😵💫😵💫😵💫😵💫😵💫😵💫 综上所述 最终大小为 40 😵💫😵💫😵💫😵💫😵💫😵💫😵💫😵💫