结构体内存中到底长啥样?

418 阅读8分钟

结构体原理(内存结构)

date: April 30, 2022 summary: 重点说其内存结构、对齐格式 type: Post

结构体的介绍及定义

结构体是一种复合结构类型,可以将其视为类和对象的前身,一般定义在头文件中

结构体的功能在于用不同的数据集合描述一个实体

但是要描述一个东西,数据是说了不算的,数据大多数情况下无法明确的区分两种东西,比如我们**想要描述一个人是小偷,仅仅描述这个人的身高、体重、穿着...这些数据是没用的,所以除了数据还要加上行为才能确实的区分一个东西,**对于上面的例子,我们要加上这个人有偷东西的行为后,才可以确认他是小偷

这也就是类(数据+行为)和结构体(数据)的区别

⚠️ 当然,结构体中也可以加入函数指针,这里不做讨论

结构体的设计难题

使用结构体的难点在于结构体的设计:一旦项目中的结构体设计出现问题,就是骨架的问题,会导致整个项目出问题,整个都需要推倒重来

浅说结构体的设计

现在有如下结构体:目的用于存储公民个人基本信息

请问:该定义是否合理?

struct tagPerson
		{
			char szName[5]; //姓名
			int nAge; //年龄
			bool cGender; //性别
			char *szPhoneNumber; //电话号码
			float fHeight; //身高
			double dblWeight; //体重
		};

答:不合理!

  1. 不应该定义nAge年龄年龄是会变化的,人多时总要刷新该数据

    QQ曾出现过这样的问题,就是这样定义的,造成的问题在于,若几年没有登录, 会导致年龄不变

    后来为了解决该问题,该为出生日期,直接计算即可

  2. 国际中性别有四种,无法使用bool

    🤨 M Male 男性——F Female 女性——U Unknow 未知——O Other 其他
  3. 身高、体重,身高、体重与本人没关系与检查测量有关系,不应该加入结构体

    身高、体重也会随时变化(若想绘制一段时间内的身高、体重变化该结构体实现不了)

    📌 该结构体为个人基本信息,若想记录身高、体重等测量信息,需要再建立一个结构体 用于专门存储这类信息

语法与原理

基本语法不做过多介绍,主要介绍原理部分:内存结构等

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)

QQ截图20220502160233.png

结构体的访问

主要两种方式:取成员.运算结构体指针—>运算

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

QQ截图20220502160621.png

那么按照上面的逻辑,若我们只调换结构体中数据类型的位置,sizeof()后大小应该是不变的,我们将顺序换一下(将dbWeight拿到cGender前面)

struct tagPerson
{
	char szName[5]; 
	int nAge;
	double dblWeight;
	char cGender;
	char *szPhoneNumber;
	float fHeight;		
};

此时奇迹发生了:仅仅换了顺序,导致sizeof()也发生了变化,也就说明了之前我们说的结构体大小的计算方式是不对的

QQ截图20220502164327.png

对齐结构

结构体的对齐单位是可以设置的,通过编译选项中的 /Zp 进行设置

结构体的对齐要求不是编译器提出的,而是网络通讯提出的

网络通讯中,经常用结构体定义数据包,某些网络协议中双方需要约定对齐值,否则会影响结构体内部布局

🍁 网路中没有约定对齐值的,其对齐值默认为1

结构体的对齐值

/Zp {1;2;4;8;16} 都可以,默认对齐值为 8(整体和各成员变量都有对齐值)

Project -> Settings -> C/C++ -> Code Generation -> Struct member alignment(结构体对齐单位)

使用默认对齐值时不会显示在编译选项中

QQ截图20220502170516.png

若要使用其余值,会在编译选项中添加 /Zp1 /Zp2...

QQ截图20220502170533.png

如何计算( ⭐⭐⭐ 吹包儿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 (否则++)

⚠️ 当结构体成员变量为结构体类型时,其MemberType设定为StructAlig

举例说明(VC6.0)

/Zp = 8

struct tagPerson
{
	char szName[5]; 
	int nAge;
	double dblWeight;
	char cGender;
	char *szPhoneNumber;
	float fHeight;		
};

的对齐格式:

QQ截图20220503105809.png 第一大步骤:(逐行分析)

char szName[5];
  1. MemberAlig = min(/Zp,sizeof(char)) = min(8,1) = 1

  2. MemberOffset % MemberAlig == 0 0 % 1 = 0

    szName[5]处于结构体中的第一个元素→MemberOffset=0

  3. 由于 0 % 1 = 0 满足 MemberOffset % MemberAlig == 0 所以无需做额外变化

    所以szName[5]被装载到Offset = 0的位置

int nAge;
  1. MemberAlig = min(/Zp,sizeof(int)) = min(8,4) = 4

  2. MemberOffset % MemberAlig == 0 6 % 4 ≠ 0

    szName[5]MemberAlig为 1 且MemberOffset=0, 0+1*5 = 5 → nAge目前的的MemberOffset=6

  3. 由于 6 % 4 ≠ 0 不满足 MemberOffset % MemberAlig == 0 所以 ++ 7 % 4 ≠ 0 再++ 8 % 4 = 0

    所以nAge的实际MemberOffset = 8 (不是6)

    所以nAge被装载到Offset = 8的位置

double dblWeight;
  1. MemberAlig = min(/Zp,sizeof(double)) = min(8,8) = 8

  2. MemberOffset % MemberAlig == 0 12 % 8 ≠ 0

    nAgeMemberAlig为 4 且MemberOffset=8, 4+8 = 12 → dblWeight目前的MemberOffset=12

  3. 由于 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;
  1. MemberAlig = min(/Zp,sizeof(char)) = min(8,1) = 1

  2. MemberOffset % MemberAlig == 0 24 % 8 = 0

    dblWeightMemberAlig为 8 且MemberOffset=16, 16+8 = 24 → cGender目前的MemberOffset=24

  3. 由于 24 % 8 = 0 满足 MemberOffset % MemberAlig == 0 所以无需做额外变化

    cGenderMemberOffset = 24

    所以dblWeight被装载到Offset = 24的位置

char *szPhoneNumber;
  1. MemberAlig = min(/Zp,sizeof(char *)) = min(8,4) = 4

  2. MemberOffset % MemberAlig == 0 25 % 4 ≠ 0

    cGenderMemberAlig为 1 且MemberOffset=24, 1+24 = 25 → szPhoneNumber目前的MemberOffset=25

  3. 由于 25 % 4 ≠ 0 不满足 MemberOffset % MemberAlig == 0 所以 ++ 26 % 4 ≠ 0 再++ 27 % 4 ≠ 0 再++ 28 % 4 = 0

    所以szPhoneNumber的实际MemberOffset = 28 (不是25)

    所以szPhoneNumber被装载到Offset = 28的位置

float fHeight;	
  1. MemberAlig = min(/Zp,sizeof(float)) = min(8,4) = 4

  2. MemberOffset % MemberAlig == 0 32 % 4 = 0

    szPhoneNumberMemberAlig为 4 且MemberOffset=28, 4+28 = 32 → fHeight目前的MemberOffset=32

  3. 由于 32 % 4 = 0 满足 MemberOffset % MemberAlig == 0 所以无需做额外变化

    所以fHeightMemberOffset = 32

    所以fHeight被装载到Offset = 32的位置

第一大步骤end

第二大步骤

设结构体变量的对齐值为StructAlig

  1. StructAlig = max(sizeof(char),sizeof(int),sizeof(double),sizeof(char),sizeof(char),sizeof(float)) = max(1,4,8,1,1,4) = 8
  2. StructAlig = min(Zp,StructAlig) = min(8,8) = 8
  3. StructSize % min(Zp,StructAlig) == 0 = (32 + 4) % 8 ≠ 0 再++ → → 40 % 8 = 0 QQ截图20220503111528.png 😵‍💫😵‍💫😵‍💫😵‍💫😵‍💫😵‍💫😵‍💫😵‍💫 综上所述 最终大小为 40 😵‍💫😵‍💫😵‍💫😵‍💫😵‍💫😵‍💫😵‍💫😵‍💫