三、结构体成员的访问
结构变量的成员是通过点操作符
[.]
访问的。点操作符接受两个操作数
- 这一点我们上面已经使用过了,就不多说,主要再介绍一种使用
[->]操作符
的形式进行访问,这一点我们在指针章节就有说到过,所以我们可以可以称其为结构体指针 - 有时候我们得到的不是一个结构体变量,而是指向一个结构体的指针。例如下面将一个结构体变量的地址给到一个结构体指针的,那就可以通过这个结构体指针去访问到这个结构体变量的成员了
struct Stu { char name[20]; int age; }; int main(void) { struct Stu s1 = { "zhangsan", 20 }; printf("普通形式访问:%s %d\n\n", s1.name, s1.age); struct Stu* ss = &s1; printf("指针形式:%s %d\n", ss->name, ss->age); return 0; }
- 那可以访问了,可不可以去进行一个修改呢?当然是可以的
- 例如下面又定义了一个结构体,然后通过乱序的方式去进行了一个初始化
struct S { char name[20]; int age; }; int main(void) { struct S s = { .age = 22, .name = "zhangsan" }; }
- 下面是对成员变量进行修改的方式,你觉得正确吗?对于
s.age = 30
;来说是没有问题的,但是呢对于姓名的修改来说其实是存在问题的,在数组章节我们有说到过对于【数组名】来说指的就是首元素地址
s.age = 30; s.name = "zhangsanfeng";
- 所以单单拿到一个数组的首元素地址,是无法对整个字符数组进行一个修改的,要么通过指针的形式遍历这个字符串做一一修改,不过这里我想要从整体的修改那么就得使用到一个有关字符串的库函数strcpy,不了解的可以去学习一下
strcpy(s.name, "zhangsanfeng");
四、结构体内存对齐【⭐】
1、前言
在结构体章节,我们掌握了结构体的基本使用,但是现在我要你去计算一个结构体的大小,你会怎么做呢?
- 现在我定义了两个结构体,通过观察可以发现它们内部的成员变量都是一样的,均有
c1
、c2
、i
三个成员变量,那此时分别去计算它们两个结构体的大小, 最后的结果会是多少呢?会是一样的吗
struct S1 { char c1; int i; char c2; }; struct S2 { char c1; char c2; int i; }; int main(void) { printf("%d\n", sizeof(struct S1)); printf("%d\n", sizeof(struct S2)); return 0; }
- 通过运行可以发现两者是不一样的,这是为什么呢?如果你没有结构体内存对齐的相关知识,那相信你一定会这么去计算:
- 在结构体S1中,
c1
的类型为【char】,是1个字节; i
的类型是【int】,是4个字节c2
的类型为【char】,是1个字节;
- 那么最后的结果就是
1 + 4 + 1 = 6B
,可事实呢,原不止这些。。。
结构体偏移量计算:offsetof
- 就上面这么来看还是看不出什么细节的内容,给读者介绍一个宏叫做
offsetof
,它可以==用来计算结构体成员相对于起始位置的偏移量==
它的第一个参数是结构体类型,第二个参数是结构体成员
printf("%d\n", offsetof(struct S1, c1)); printf("%d\n", offsetof(struct S1, i)); printf("%d\n", offsetof(struct S1, c2));
- 最后,计算出来的结果分别是【0】【4】【8】,那我们可以通过画内存图来看看结构体中的三个成员变量在内存中究竟是如何分布的
- 可以看出,因为总的结构体大小为12B,可是在放完这3个成员后中间空出了三个位置,并且对于最后在c放完之后还没有到达12B,所以还得再浪费3个空间的废位置
为什么会出现上面这样的现象呢?对于结构体内存对齐的规则是怎样,让我们继续看下去👇
2、规则介绍
- 第一个成员在与结构体变量偏移量为0的地址处
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处==对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值==
- 【VS中默认的值为8、Linux环境默认不设对齐数(对齐数是结构体成员自身的大小)】
- 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍
例题的分解与细说
知晓了上面这些规则后,我们再来回顾一下上面这个结构体的大小该如何计算
- 假设我这里创建一个结构体变量叫做
ss
,它的起始地址就从0开始,所以根据第一条规则,第一个成员变量在与结构体变量偏移量为0的地址处,而且它的类型还是char
,所以只占1个内存单元
- 接下去看第二个成员变量
i
,其为整型所以在内存中就需要存储4个字节的大小,此时便要拿其和VS下默认对齐数8去进行比较,取较小的值4 - 算出来【4】之后便要对齐到4整数倍的地址处,那就是4这块空间,往下一直占用4个字节,这就是成员变量i在这个结构体中的内存占用分布
- 那既然这个
i
是从4的位置开始放的,中间空出来的位置就不会再放置其他成员变量了,那么这个3个空间也就浪费了
- 接下去放置第三个成员变量
c2
,char类型的变量为1个字节,和8比较取小就是1,那就要将其放到1整数倍的地址处,那其实任何空间都是可以的,直接放到这个【8】的位置就行 - 那截止目前为止这个结构体中的所有成员变量都放置完了,此时去计算一个所占的内存空间就可以发现只有9个字节。但是在一开始我们计算的这个结构体的大小为12个字节,可是现在还差3个字节,所以最后就要去进行一个填充。但是,为什么呢?
- 这就要用到第三条规则了:==结构体总大小为最大对齐数的整数倍==
- 那在这么计算下来之后,就可以知道结构体中的最大对齐数为4,那么【9】、【10】、【11】都不是它的整数倍,只有【12】是它的整数倍的地址处(注意这里是地址处!),因此我们需要填充3个字节,此时从0 ~ 11就有12个字节了,便为4的整数倍 👉这就是【12】如何被计算出来的全过程,你听懂了吗?
看完了,这个结构体后,还记得结构体S2吗,我再来讲一道,当然你也可以试着自己写写画画看👈
- 首先还是一样,
c1
放在这个与结构体变量偏移量为0的地址处,而且它的类型还是char
,所以只占1个内存单元 - 接下去还是一样,在放置第二个成员变量开始就要考虑【对齐数】了,
char
所占的字节为1B,与8去进行比较一下就可以知道1来得小,那我们直接放在偏移处为1的地方就可以了,此时在内存中也只占了2个字节
- 接下去放置第三个成员变量【i】,大小为4个字节小于8因此选择在4的整数倍的地址处开始放置这个变量,整型占4个字节,所以一直占用到偏移量为7的地方
- 接下去就是计算整个结构体的大小,最大对齐数为4,所以要为4的整数倍,此时去计算一下得知从0 ~ 7偏移了7个字节,占用了8个空间,刚好为4的整数倍,所以结构体S2的大小为【8】是这么算出来的,你明白了吗?
3、习题演练
通过上面两道例题的讲解,相信你对如何去计算结构体大小一定有了一个自己的认识,接下去就让我们趁热打铁🔥来做两道题目再练一练,看看自己是否真的掌握了
练习①
你可以先试着自己做一做,然后和我对一下是否正确
struct S3 { double d; char c; int i; }; printf("%d\n", sizeof(struct S3));
【分析】:
- 首先看到第一个成员变量,从偏移量为0的地址处开始放起,因为
double
类型的数据在内存中占8个字节,所以一直占用偏移处为7的地方
- 对于第二个成员变量【c】,类型为
char
,所以在内存中占用1个字节,那直接放在偏移量为8的地址处即可 - 接下去来安排第三个成员变量【i】,整型占用4个字节,比VS下默认对齐数8来得小所以【对齐数为4】,去寻找4整数倍的地址处,【9】、【10】、【11】都不是,【12】是4的整数所偏移的地址处,从此处开始往下数4个字节的空间,刚好放满15
- 最后我们便去计算整个结构体的大小,为最大对齐数的整数倍,最大对齐数是8,计算一下放置三个成员变量占了16个空间,刚好是8的整数倍,因此16即为结构体的大小
运行结果如下:
也可以通过【offsetof】来验证一下
练习②
接下去再来做一道练习,涉及结构体嵌套的问题,对应的需要使用到规则4,忘记了可以翻上去看看👈
struct S3 { double d; char c; int i; }; struct S4 { char c1; struct S3 s3; //成员变量为另一个结构体 double d; };
因为本题的结构体比较大,所以就标出4的整数倍所在的地址
- 首先还是一样,来看到第一个成员变量【c1】,放到与结构体变量偏移量为0的地址处,又因为类型为
char
,所以只占一个字节的空间
- 接下去,就是嵌套的结构体s3,此时我们要对齐到s3这个结构体中最大对齐数的整数倍处,那么最大对齐数就是【8】,所以要从8的地址处开始往下放置,那要占用多少空间呢?这就是s3这个结构体的大小【16】,所以一直往下数16个空间即可,一直到23这个地址处
- 那么中间的这7个位置就算是浪费了👈
- 最后就是这个【d】,与VS中的默认对齐数一致,所以为【8】,下一个24刚好为8整数倍的地址处,所以从这开始放,
double
类型的数据在内存中占8个字节,所以一直到31的地址处 - 然后来算整个结构体s4的大小,为所有最大对齐数(含嵌套结构体的对齐数)的整数倍,也就是取s3和s4中的最大对齐数,那也就是【8】,计算一下结构体s4所占的内存空间为32,刚好为8的整数倍,所以整个结构体的大小即为32
运行结果如下:
可以通过【offsetof】再来验证一下