目录
1 . 结构体类型的声明
1.1 结构的声明
1.2 结构体变量的创建与初始化
1.3 结构体的特殊声明
1.4 结构体的自引用
2. 结构体内存对齐
2.1 对齐规则
2.2 为什么存在内存对齐
2.3 修改默认对齐数
3. 结构体传参
4.结构体实现位段
4.1 位段的内存分配
1 . 结构体类型的声明
1.1 结构的声明
struct tag
{
member-list;
}variable-list;
假如描述一个学生
struct Stu
{
int age;
char name[20];
char sex[5];
char id[20];
}
1.2 结构体变量的创建与初始化
struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
};
int main()
{
//按照结构体成员的顺序初始化
struct Stu s = { "张三", 20, "男", "20230818001" };
printf("name: %s\n", s.name);
printf("age : %d\n", s.age);
printf("sex : %s\n", s.sex);
printf("id : %s\n", s.id);
//按照指定的顺序初始化
struct Stu s2 = { .age = 18, .name = "lisi", .id = "20230818002", .sex = "⼥" };
printf("name: %s\n", s2.name);
printf("age : %d\n", s2.age);
printf("sex : %s\n", s2.sex);
printf("id : %s\n", s2.id);
return 0;
}
1.3 结构体的特殊声明
在声明结构体的时候,可以进行不完全声明(又称匿名结构体)
struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
}a[20], * p;
上述两个结构体在声明的时候省略了结构体标签
在某些情况下,如果只是想使用一次结构体,就可以使用匿名结构体
在此基础上,下面代码合法吗
p = &x;
1.4 结构体的自引用
结构体中包含一个类型为该结构体本身成员是否可行?
看下列代码
struct Node
{
int data;
struct Node next;
}
分析一下不难发现,其实是不行的,因为一个结构体中再包含一个同类型的结构体变量,这样结
构体变量的大小就会无穷大。
struct S
{
int n;
struct S* next;
};
但是如果时包含和自己相同类型的指针,是可行的,在x86或x64的环境下,指针的大小无非就是
4/8字节。
在结构体自引用使用的过程中,夹杂了 typedef 对匿名结构体类型重命名,也容易引入问题,看看
typedef struct
{
int data;
S* next;
}S;
2. 结构体内存对齐
看一段代码
struct S1
{
char c1;
int i;
char c2;
}s;
int main()
{
printf("%zd \n",sizeof(s));
return 0;
}
如果只按成员的大小来看的话,该结构体应该只占用6个字节就够了
但程序运行起来后可以发现是12个字节。
这就涉及到结构体内存对齐。
2.1 对齐规则
struct S2
{
char c1;
char c2;
int i;
};
一共8个字节,按照规则3来说,为最大对齐数的整数倍,那就是2* 4 = 8个字节
struct S3
{
double d;
char c;
int i;
};
一共15个字节,按照规则3来说,为最大对齐数的整数倍,那就是4* 4 = 16个字节
struct S4
{
char c1;
struct S3 s3;
double d;
};
其中嵌套了s3,已经知道s3的大小是16个字节,其中最大对齐数是8,那么就从偏移量8开始占16个字节。
一共32个字节,按照规则3来说,为最大对齐数的整数倍,那就是4* 8 = 32个字节
2.2 为什么存在内存对齐
struct S1
{
char c1;
int i;
char c2;
}s;
struct S2
{
char c1;
char c2;
int i;
};
同样的成员类型,我们可以发现S1占12个字节,S2就只占8个字节了。
2.3 修改默认对齐数
#pragma 这个预处理指令,可以改变编译器的默认对齐数。
#include <stdio.h>
#pragma pack(1)//设置默认对⻬数为1
struct S
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的对⻬数,还原为默认
int main()
{
printf("%d\n", sizeof(struct S));
return 0;
}
3. 结构体传参
struct S
{
int data[1000];
int num;
};
struct S s = { {1,2,3,4}, 1000 };
//结构体传参
void print1(struct S s)
{
printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{
printf("%d\n", ps->num);
}
int main()
{
print1(s); //传结构体
print2(&s); //传地址
return 0;
}
print2更好,因为print1在传参时是传值调用,这个值有多大就得开辟多大的空间,仅是一个data数
组就要了4000个字节的空间。
而print2在传参的时候是传址调用,传地址过去,大小无非就是4 / 8个字节,效率更高。
4.结构体实现位段
4.1 位段的定义
位(二进制位)
struct A
{
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 30;
};
struct B
{
int _a;
int _b ;
int _c ;
int _d ;
};
int main()
{
printf("%zd\n", sizeof(struct A));
printf("%zd\n", sizeof(struct B));
return 0;
}
4.1 位段的内存分配
1. 位段的成员可以是 int unsigned int signed int 或者是 char 等类型
struct S
{
char a : 3;
char b : 4;
char c : 5;
char d : 4;
};
int main()
{
struct S s = { 0 };
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;
printf("%zd\n", sizeof(s));
return 0;
}
4.3 位段的应用
下图是网络协议中,IP数据报的格式,我们可以看到其中很多的属性只需要几个bit位就能描述,这
里使用位段,能够实现想要的效果,也节省了空间,这样网络传输的数据报大小也会较小⼀些,对
网络的畅通是有帮助的。