结构体struct和联合体union最全讲解
摘要:⾸先感谢三位博主,并做出总结.
⾸先了解⼀下struct的储存结构:
⼀、结构体的访问
1.结构体成员的的直接访问,如下结构体:
struct A{
int a;
long *b;
char c[20];
};
struct A com;
结构体成员通过操作符"."访问,表达式com.a的结果是个数组名,可以把它使⽤在任何可以使⽤数组名的地⽅,com.a[4],将选择⼀个数组元素。
2、结构体成员的间接访问
struct A *p;
可以使⽤(*p).a访问结构体成员,但这种形式有点不简洁所以使⽤操作符"->"来访问结构体成员,箭头操作符对左操作数执⾏间接访问来获取指针所指向的结构,然后根据右操作数来访问⼀个成员,p->a。
⼆、结构体的⾃引⽤
struct B{
int a;
struct B b;
int c;
};
这种引⽤是不合法的,因为b是⼀个完整的结构,第⼆个成员⼜是另⼀个完整的结构,还包括她⾃⼰的成员,这样会循环下去⽆法及所结构体的⼤⼩。
struct B{
int a;
struct B *b;
int c;
};
这种声明是合法的,b现在是⼀个指针它所占的字节数是已知的,可以计算出结构体的⼤⼩,这种⾃引⽤是合法的。
三、结构体、指针和成员
typedef struct{
int a;
short b[2];
}Ex1;
typedef struct{
int a;
char b[3];
Ex1 c;
struct Ex1 d;
}Ex2;
Ex2 x={1,"My",{2,{3,4}},0};
Ex2 *p=&x;
如下图来表⽰此结构:
创建⼀个指向整型的指针:int *p1;若要使它指向整型成员a,应使⽤&取得⼀个指向p->a的指针:p1=&p->a.
访问嵌套的结构体:p->c.a即为访问c结构体中的整形a。
访问指针成员:定义另⼀结构:Ex y; x.d=&y;则Ex->d->c.b[1]=4;则表⽰如下图空间:
四、结构体的内存对齐
对齐规则
每个特定平台上的编译器都有⾃⼰的默认“对齐系数”,也可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这⼀系数,其中的n就是你要指定的“对齐系数”。
规则:
1、数据成员对齐规则:struct数据成员,第⼀个数据成员放在offset为0的地⽅,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员⾃⾝长度中,⽐较⼩的那个进⾏。(即第⼀个数据成员以后的成员的偏移地址为#pragma pack指定的数值和这个数据成员⾃⾝长度中,⽐较⼩的那个的整数倍)
2、struct的整体对齐规则:在数据成员完成各⾃对齐之后,结构体本⾝也要进⾏对齐,对齐将按照#pragma pack指定的数值和struct最⼤数据成员长度中,⽐较⼩的那个进⾏。
3、当#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的⼤⼩将不产⽣任何效果。
当结构体⾥⾯包含另⼀结构体时,直接将该结构体中的内容代换进去,计算其总的存储空间即可。
例:(在此平台上int占4个字节)
虽然A和A1所包含的成员相同,但A占了12个字节,A1占8个字节,所以在声明中对结构体列表的排列,因该让边界要求严格的成员⾸先出现(数据成员⾃⽣长度⼤的先出现)
1.联合体union的基本特性——和struct的同与不同
union,中⽂名“联合体、共⽤体”,在某种程度上类似结构体struct的⼀种数据结构,共⽤体(union)和结构体(struct)同样可以包含很多种数据类型和变量。
不过区别也挺明显:
结构体(struct)中所有变量是“共存”的——优点是“有容乃⼤”,全⾯;缺点是struct内存空间的分配是粗放的,不管⽤不⽤,全分配。
⽽联合体(union)中是各变量是“互斥”的——缺点就是不够“包容”;但优点是内存使⽤更为精细灵活,也节省了内存空间。
2.双刃剑——多种访问内存途径共存
⼀个例⼦了然:
1. //example
2. #include<stdio.h>
3. union var{
4. long int l;
5. int i;
6. };
7. main(){
8. union var v;
9. v.l = 5;
10. printf("v.l is %d\n",v.i);
11. v.i = 6;
12. printf("now v.l is %ld! the address is %p\n",v.l,&v.l);
13. printf("now v.i is %d! the address is %p\n",v.i,&v.i);
14. }
15. 结果:
16. v.l is 5
17. now v.l is 6! the address is 0xbfad1e2c
18. now v.i is 6! the address is 0xbfad1e2c
所以说,管union的叫共⽤体还真是贴切——完全就是共⽤⼀个内存⾸地址,并且各种变量名都可以同时使⽤,操作也是共同⽣效。如此多的access内存⼿段,确实好⽤,不过这些“⼿段”之间却没法互相屏蔽——就好像数组+下标和指针+偏移⼀样。
上例中我改了v.i的值,结果v.l也能读取,那么也许我还以为v.l是我想要的值呢,因为上边提到了union的内存⾸地址肯定是相同的,那么还有⼀种情况和上边类似:
⼀个int数组变量a,⼀个long int(32位机中,long int占4字节,与int相同)变量b,我即使没给int变量b赋值,因为数据类型相同,我使⽤int变量b也完全会拿出int数组a中的a[0]来,⼀些时候⼀不⼩⼼⽤上,还以为⽤的就是变量b呢~
这种逻辑上的错误是很难出来的(只有当数据类型相去甚远的时候稍好,出个乱码什么的很容易发现错误)。
PS:感谢热⼼⽹友的提醒“在union定义结束时加分号”,其实是可以不加的,因为他不在主函数内,不是执⾏的语句,如果是主函数内声明的union就必须加分号了,在主函数内不加分号就涉及到基础常识了——没有分号隔开怎能叫⼀句。
3.联合体union和⼤⼩端(big-endian、little-endian):
1. #include<stdio.h>
2. union var{
3. char c[4];
4. int i;
5. };
6.
7. int main(){
8. union var data;
9. data.c[0] = 0x04;//因为是char类型,数字不要太⼤,算算ascii的范围~
10. data.c[1] = 0x03;//写成16进制为了⽅便直接打印内存中的值对⽐
11. data.c[2] = 0x02;
12. data.c[3] = 0x11;
13. //数组中下标低的,地址也低,按地址从低到⾼,内存内容依次为:04,03,02,11。总共四字节!
14. //⽽把四个字节作为⼀个整体(不分类型,直接打印⼗六进制),应该从内存⾼地址到低地址看,0x11020304,低位04放在
低地址上。
15. printf("%x\n",data.i);
16. }
结果:
11020304
证明我的32位linux是⼩端(little-endian)
4.联合体union所占内存空间⼤⼩:
前边说了,⾸先,union的⾸地址是固定的,那么,union到底总共有多⼤?根据⼀些⼩常识,做个不严谨不⾼深的基础版验证吧。
根据:分配栈空间的时候内存地址基本上是连续的,⾄少同类型能保证在⼀起,连续就说明,我如果弄三个结构体出来,他们三个地址应该连着,看⼀下三个地址的间隔就知道了。
1. #include<stdio.h>
2. union sizeTest{
3. int a;
4. double b;
5. };
6. main(){
7. union sizeTest unionA;
c语言struct用法例子8. union sizeTest unionB;
9. union sizeTest unionC;
10.
11. printf("the initial address of unionA is %p\n",&unionA);
12. printf("the initial address of unionB is %p\n",&unionB);
13. printf("the initial address of unionC is %p\n",&unionC);
14. }
打印,可以看到结果:
the initial address of unionA is 0xbf9b8df8
the initial address of unionB is 0xbf9b8e00
the initial address of unionC is 0xbf9b8e08
很容易看出,8,0,8,这间隔是8字节,按double⾛的。
怕不保险,再改⼀下,把int改成数组,其他不变:
1. union sizeTest{
2. int a[10];
3. double b;
4. };
此时sizeTest⼤⼩为16字节,因为需要当数组为a[10] size⼤⼩为10,取较⼩值的最⼩公倍数.8*2 = 16.
5.联合体union适⽤场合:
有了前边那个验证,基本可以确认,union的内存是照着⾥边占地⼉最⼤的那个变量分的。
也就可以⼤胆的推测⼀下,这种union的使⽤场合,是各数据类型各变量占⽤空间差不多并且对各变量同时使⽤要求不⾼的场合(单从内存使⽤上,我觉得没错)。
像上边做的第⼆个测试,⼀个数组(或者更⼤的数组int a[100]),和⼀个或者⼏个⼩变量写在⼀个union⾥,实在没什么必要,节省的空间太有限了,还增加了⼀些风险(最少有前边提到的逻辑上的风险)。所以,从内存占⽤分析,这种情况不如直接struct。
不过话说回来,某些情况下虽然不是很节约内存空间,但是union的复⽤性优势依然存在啊,⽐如⽅便多命名,这种“⼆义性”,从某些⽅⾯也可能是优势。这种⽅法还有个好处,就是某些寄存器或通道⼤⼩有限制的情况下,可以分多次搬运。
6.本质&进阶:
根据union固定⾸地址和union按最⼤需求开辟⼀段内存空间两个特征,可以发现,所有表⾯的定义都是虚的,所谓联合体union,就是在内存给你划了⼀个⾜够⽤的空间,⾄于你怎么玩~它不管~!(何⽌是union和struct,C不就是玩地址么,所以使⽤C灵活,也容易犯错)
没错,union的成员变量是相当于开辟了⼏个接⼝(即union包含的变量)!但是,没开辟就不能⽤了?
当然也能⽤!
写个⼩测试:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论