C语言结构体大小的计算

667 阅读6分钟

1.结构体的声明

要知道怎么计算结构体大小我们首先要了解结构体

1.1什么是结构体

结构是一些值的集合,这些值称为成员变量.结构的每个成员可以是不同类型的变量.

1.2结构体的声明和定义

struct tag
{
 member-list;
}variable-list;

member-list;

  • 此处是结构体成员
  • 可以有多个成员变量
  • 成员类型可以是不同类型的变量

variable-list;

  • } 之后的此处是定义的变量列表,在结构体创建初同时定义了一个结构体变量
  • 此处定义的变量是全局变量
  • 也可以在函数中 使用 struct tag abc; (tag为结构体名,abc为变量名) 方式来定义变量(局部变量)

例如此处描述一个学生:

struct Stu
{
 char name[20];//名字
 int age;//年龄
 char sex[5];//性别
 char id[20];//学号
}s4,s5; //分号不能丢


int main()
{
	struct Stu s1;
	struct Stu s2;
	struct Stu s3;

	return 0;
}

此处的Stu就是一个结构体,内部成员描述学生的属性,s1,s2,s3,s4,s5都是定义的结构体变量.s4,s5是全局变量,s1,s2,s3是局部变量.

1.3匿名结构体

对于上述结构体的声明 有一种特殊的存在

struct
{
 int a;
 char b;
 float c;
}x;
struct
{
 int a;
 char b;
 float c;
}*p;

细心的你应该发现了,上述的声明方式没有名称,这种类型叫做匿名结构体,由于没有名称,所以只会在声明结构体的时候,同时定义需要的结构体变量.

需要注意的是上述的两种结构体虽然成员内容相同,但是在内存中完全是不同的两个结构体.

所以基于上述代码 p = &x; 这样的代码是不合法的.

1.4结构体的自引用

struct Node
{
 int data;
 //1.struct Node next;
 //2.struct Node* next;
};

如果结构体需要在成员中构建自己的变量的时候,应该使用 方式1 还是 方式2 呢. 如果你也觉得迷茫.那么请你看这么一段代码.

sizeof(struct Node);

如果使用方式1的话,那上述代码算出来的结果应该是多少呢. 我们发现会进入无限循环,无法算出结果.这在计算机种有限资源的容器中是不允许出现的.所以方式2才会正确的方式.也就是下述代码.

struct Node
{
 int data;
 struct Node* next;
};

1.5结构体改名

结构体也是可以改名的(typedef关键字)

//结构体改名	
typedef struct Student
{
	int id;
	char name[20];
}Stu;

int main()
{
	Stu s1;
	return 0;
}

我们可以发现在改名之后,我们可以更方便的创建结构体变量.

改名后的自引用

知道结构体可以改名后,肯定有人要问 那我可不可以,使用改名之后的结构体名称来进行自引用.也就是下述代码.

XXX
//匿名
typedef struct
{
 int data;
 Node* next;
}Node;
//非匿名
typedef struct Node
{
 int data;
 Node* next;
}Node;

简洁明了,不可以.这是不允许的.想要自引用,只能使用原名称的指针类型来引用.

//这是正确的引用方式
typedef struct Node
{
 int data;
 struct Node* next;
}Node;

此块代码是正确的引用方式

2.结构体的初始化

struct Point
{
 int x;
 int y;
}p1; //声明类型的同时定义变量p1
struct Point p2; //定义结构体变量p2
//初始化:定义变量的同时赋初值。
struct Point p3 = {4, 5};
struct Stu        //类型声明
{
 char name[15];//名字
 int age;      //年龄
};
struct Stu s = {"zhangsan", 20};//初始化
struct Node
{
 int data;
 struct Point p;
 struct Node* next;
}n1 = {10, {4,5}, NULL}; //结构体嵌套初始化
struct Node n2 = {20, {5, 6}, NULL};//结构体嵌套初始化

int main()
{
	struct Node head;
	//定义后初始化
	head.data= 0;
	head.p = p3;
	head.next = NULL;

	Node h2 = {2,NULL,NULL};//定义时初始化
}

结构体初始化有两种

  • 在定义的同时初始化
  • 在定义之初不初始化,之后在对成员分别进行初始化

而在定义之后,对成员的操作方式也是有两种

  • "." 操作符:针对普通类型
  • "->"操作符:针对指针类型
struct date // 声明一个结构体类型
{
    int month;
    int day;
    int year;
}

struct student
{
    int num;
    char name[20];
    char sex;
    int age;
    struct date birthday;
    char addr[30];
}student1, *student2;

int main()
{
	//直接使用"."操作符进行操作.
	student1.num = 10;
	//bitthday成员也是一个结构体,可以嵌套使用"."操作符
	student1.birthday.month = 5;
	student1.age = 20;
	student1.age ++;
	
	//student2是指针类型,使用"->"操作符
	student2->age = 18;
	//由于优先级问题需要带括号
	(&student2).num = 31;
}

当然指针类型也是可以使用"."操作符的.只需要解引用即可,但是注意需要带括号,因为"."操作符优先级高于解引用操作符.

3.结构体大小的计算

了解了结构体以及基本操作,那我们来看看结构体的大小应该怎么计算

3.1结构体对齐

结构体对齐规则

  1. 第一个成员在与结构体变量偏移量为0的地址处。
  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 VS中默认的值为8
  3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。(最大对齐数不包含vs默认值)
  4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

3.2结构体大小的计算

struct S1
{
 char c1;
 int i;
 char c2;
};
printf("%d\n", sizeof(struct S1));

image.png

灰色为浪费掉的空间. 根据上述规则

  1. 首先比较c1的大小和默认对齐数的大小. 显然c1的小以为c1的大小为1.然后根据1和当前偏移量对比,如果偏移量为0,则从当前位置开始存入数据,如果偏移量不为0,将偏移值移动到1的倍数处.存入c1的值,0位置.
  2. 然后比较下一个,也就是 i 的大小和默认值得大小的最小值,得到的值为4.然后根据4和当前偏移量比较,不为0则移动到4的倍数处.存入i的值,也就是4位置.
  3. 然后比较下一个,c2的大小和默认值得大小的最小值,得到的值为1.然后根据1和当前偏移量比较,不为0则移动到1的倍数处.存入c2的值,也就是9位置.
  4. 最后计算总大小.成员的最大对齐数为4(不包含vs的默认值),当前偏移量为9,需要对齐为4的倍数.也就是12.所以结构体大小就是0到对齐位置之前的内存大小,也就是12.

所以结构体最终大小为12.

3.3补充

可以使用#pragma pack(8)来更改默认对齐数

//在创建结构体之前设置
#pragma pack(1)//设置默认对齐数为1
//使用完后可以使用无参数的来还原为默认
#pragma pack()//取消设置的默认对齐数,还原为默认