一. 结构体类型的声明
二. 结构体变量的创建和初始化
三. 结构体内存对⻬
四. 结构体传参
1.结构的声明
struct tag
{
member-list;
}variable-list;
例如描述⼀个学⽣:
struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
};
2. 1结构体变量的创建和初始化
2. 2结构的特殊声明
在声明结构的时候,可以不完全的声明。
struct
{
int a;
char b;
float c;
};
例如上图,但是我们要知道匿名的结构体类型,如果没有对结构体类型重命名的话,基本上只能使⽤⼀次。
3. 1结构体内存对齐
这是结构体中非常重要的知识对⻬规则
⾸先得掌握结构体的对⻬规则:1.结构体的第⼀个成员对⻬到和结构体变量起始位置偏移量为0的地址处
2.其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。
对⻬数=编译器默认的⼀个对⻬数与该成员变量⼤⼩的较⼩值。VS 中默认的值为 8 -Linux中gcc没有默认对⻬数,对⻬数就是成员⾃⾝的⼤⼩
3.结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的 整数倍。
4.如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构 体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍。
通过一段代码来深入理解结构体内存对齐吧
用画图的方式来分析
struct S1
{
char c1;
int i;
char c2;
};
printf("%d\n", sizeof(struct S1));
结构体变量S在内存中开辟出一块空间, 结构体的第⼀个成员对⻬到和结构体变量起始位置偏移量为0的地址处,char类型占一个字节,占的空间如图所示
2.其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。也就是说i变量要对齐到(对⻬数=编译器默认的⼀个对⻬数与该成员变量⼤⼩的较⼩值)的整数倍的地址处,而vs中的值为8,i变量大小是4,二者较小值为4,所以要对其4的整数倍的地址处,1.2.3.4在4处开始存放大小为4个字节的空间,如图所示
3.c2变量大小为一个字节,与8对比(vs中对齐数为8),二者较小值为1,对齐到1的整数倍的地址处,任何数都是1的整数倍,所以直接往下一个地址处存放就行了。
4.结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的 整数倍。上述3个成员变量的对齐数分别是1、4、1其中4是最大的,所以总大小应该为4的整数倍4、8、12,4和8都不能存入(前面已经使用了9个字节的空间了),所以总大小应该为12。
接下来如果碰见了嵌套型的结构体该如何去做呢,也用一个代码来分析
struct S3
{
double d;
char c;
int i;
};
printf("%d\n", sizeof(struct S3));
struct S4
{
char c1;
struct S3 s3;
double d;
};
printf("%d\n", sizeof(struct S4));
1.首先我们去计算S3的大小,第一个double类型变量存入在偏移量为0的地址处,大小是8,第二个char类型变量大小为1,存入到对齐数为1的整数倍的地址处,也就是向下紧接着存一个字节的空间,第三个int类型变量大小为4,存入到对齐数为4的整数倍的地址处,因为此时所占空间是9,不满足4的整数倍,所以要跳过3个字节处在开始存放变量大小为4个字节的空间。现在所占用的大小是16个字节,而总大小是最大对齐数的整数倍,分别是8、1、4其中8最大,所以总大小为8的整数倍,16正好满足,所以该结构体的总大小为16
2.计算完S3大小以后,我们来看这个嵌套类型,首先char类型存放一个字节,然后存放结构体类型,如果出现了嵌套结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,也就是S3的成员,很明显最大对齐数是8,从0开始偏移8个字节存入,完成以后再存double类型变量,对齐数为8且此时正好所占字节数为24,紧接着向下存入大小为8个字节的空间,现在占用32个字节。存完以后,结构体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍。也就是包含S4中成员的对齐数,S3:8、4、1;S4:1、8 所以最大对齐数还是8,8的整数倍为32满足条件,所以该结构体的总大小为32,图示如下
3.2 为什么存在内存对⻬?
⼤部分的参考资料都是这样说的:
1. 平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定 类型的数据,否则抛出硬件异常
2. 性能原因: 数据结构(尤其是栈)应该尽可能地在⾃然边界上对⻬。原因在于,为了访问未对⻬的内存,处理器需要 作两次内存访问;⽽对⻬的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字节,则地 址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对⻬成8的倍数,那么就可以 ⽤⼀个内存操作来读或者写值了。否则,我们可能需要执⾏两次内存访问,因为对象可能被分放在两 个8字节内存块中。
总体来说:结构体的内存对⻬是拿空间来换取时间的做法。
那在设计结构体的时候,我们既要满⾜对⻬,⼜要节省空间,如何做到: 让占⽤空间⼩的成员尽量集中在⼀起
struct S1
{
char c1;
int i;
char c2;
};
struct S2
{
char c1;
char c2;
int i;
};
S1 和 S2 类型的成员⼀模⼀样,但是 S1 和 S2 所占空间的⼤⼩有了⼀些区别。
3.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;
}
这段代码最后打印的结果是6, vs本来对齐数是8,现在变为1了,所以当int类型变量存入时的地址就发生了改变,原本的对齐数的较小值应该为4,现在的对齐数较小值为1了所以可以紧接着存放,图示如下
4. 结构体传参
这是结构体传参的两种方式,这两种方式哪个更好呢?答案是第二种结构体地址传参更好。为何呢?
原因: 函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。 如果传递⼀个结构体对象的时候,结构体过⼤,参数压栈的的系统开销⽐较⼤,所以会导致性能的下降。
结论: 结构体传参的时候,要传结构体的地址。
以上就是c语言中自定义类型之结构体的一些知识点的梳理和理解了。