(7条消息) 结构体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个字节,所以在声明中对结构体列表的排列,因该让边界要求严格的成员首先出现(数据成员自生长度大的先出现)

以上转载自:https://www.cnblogs.com/Blog-day/p/MY_Blog_Days-3.html

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. int main(){
  7. union var data;
  8. data.c[0] = 0x04;//因为是char类型,数字不要太大,算算ascii的范围~
  9. data.c[1] = 0x03;//写成16进制为了方便直接打印内存中的值对比
  10. data.c[2] = 0x02;
  11. data.c[3] = 0x11;
  12. //数组中下标低的,地址也低,按地址从低到高,内存内容依次为:04,03,02,11。总共四字节!
  13. //而把四个字节作为一个整体(不分类型,直接打印十六进制),应该从内存高地址到低地址看,0x11020304,低位04放在低地址上。
  14. printf("%x\n",data.i);
  15. }

结果:
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;
  8. union sizeTest unionB;
  9. union sizeTest unionC;
  10. printf("the initial address of unionA is %p\n",&unionA);
  11. printf("the initial address of unionB is %p\n",&unionB);
  12. printf("the initial address of unionC is %p\n",&unionC);
  13. }

打印,可以看到结果:

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包含的变量)!但是,没开辟就不能用了?当然也能用!

写个小测试:

  1. #include<stdio.h>
  2. union u{
  3. int i;
  4. double d;//这个union有8字节大小
  5. };
  6. main(){
  7. union u uu;
  8. uu.i = 10;
  9. printf("%d\n",uu.i);
  10. char * c;
  11. c = (char *)&uu;//把union的首地址赋值、强转成char类型
  12. c[0] = 'a';
  13. c[1] = 'b';
  14. c[2] = 'c';
  15. c[3] = '\0';
  16. c[4] = 'd';
  17. c[5] = 'e';
  18. //最多能到c[7]
  19. printf("%s\n",c);//利用结束符'\0'打印字符串"abc"
  20. printf("%c %c %c %c %c %c\n",c[0],c[1],c[2],c[3],c[4],c[5]);
  21. }

一个例子了然,我的结构体只定义了int和double“接口”,只要我获得地址,往里边扔什么数据谁管得到?这就是C语言的强大,这就是union的本质——只管开辟一段空间。

有些东西,熟悉编译原理和编译器工作过程的话,解决会更容易点,虽然我现在这方面技能不太强,不过一般问题也足够分析了。

以上转自:https://www.cnblogs.com/tianlangshu/p/5204521.html.

结构体的嵌套问题

结构体的自引用(self reference),就是在结构体内部,包含指向自身类型结构体的指针。

结构体的相互引用(mutual reference),就是说在多个结构体中,都包含指向其他结构体的指针。

1. 自引用结构体

1.1 不使用typedef时

错误的方式:

struct tag_1{
    struct tag_1 A;
    int value;
};

这种声明是错误的,因为这种声明实际上是一个无限循环,成员A是一个结构体,A的内部还会有成员是结构体,依次下去,无线循环。在分配内存的时候,由于无限嵌套,也无法确定这个结构体的长度,所以这种方式是非法的。

正确的方式: (使用指针)

struct tag_1{
    struct tag_1 *A;
    int value;
};

由于指针的长度是确定的(在32位机器上指针长度为4),所以编译器能够确定该结构体的长度。

1.2 使用typedef 时

错误的方式:

typedef struct {
    int value;
    NODE *link;
} NODE;

  这里的目的是使用typedef为结构体创建一个别名NODEP。但是这里是错误的,因为类型名的作用域是从语句的结尾开始,而在结构体内部是不能使用的,因为还没定义。

正确的方式:有三种,差别不大,使用哪种都可以。

typedef struct tag_1{
    int value;
    struct tag_1 *link;
} NODE;

struct tag_2;
typedef struct tag_2 NODE;
struct tag_2{
    int value;
    NODE *link;
};

struct tag_3{
    int value;
    struct tag_3 *link;
};
typedef struct tag_3 NODE;
2. 相互引用 结构体

错误的方式:

typedef struct tag_a{
    int value;
    B *bp;
} A;

typedef struct tag_b{
    int value;
    A *ap;
} B;

错误的原因和上面一样,这里类型B在定义之前 就被使用。

正确的方式:(使用“不完全声明”)

struct tag_a{
    struct tag_b *bp;
    int value;
};
struct tag_b{
    struct tag_a *ap;
    int value;
};
typedef struct tag_a A;
typedef struct tag_b B;

struct tag_a;
struct tag_b;
typedef struct tag_a A;
typedef struct tag_b B;
struct tag_a{
    struct tag_b *bp;
    int value;
};
struct tag_b{
    struct tag_a *ap;
    int value;
};

嵌套结构体时应注意:

结构体的自引用中,如下这种情况是非法的
struct s_ref {
 int a;
 struct s_ref b;
 char c;
};
因为结构体内部又包含自身结构体类型b,这个长度不能确定,只能向下再查找,又包含自身结构体类型b,又再向下查找,如此循环,类似于永无出口的递归调用,是非法的。

但很多时候,的确需要使用到自引用,有个技巧,如下:
struct s_ref {
 int a;
 struct s_ref *b;  //注意这句与上面相同位置的区别
 char c;
};
这是合法的,因为此处是定义了一个指向结构体的指针,指针的大小在具体的机器平台和编译器环境中都是已知的(即使不同的平台环境的定义不完全相同)。所以不会导致上述的递归死循环。是合法和可行的。但是要提醒的是:这个指针看似指向自身,其实不是,而是指向同一类型的不同结构。
链表和树的数据结构就都使用到此技巧。自身的结构体指针指向下一节点或者下一子树的地址。

这里有一种情况值得注意:
typedef struct {   //这里是结构体类型定义
 int a;
 s_ref *b;  //注意这句引用了结构体类型名
 char c;
}s_ref ;
这个结构体类型定义是为了定义类型名s_ref,但却失败了。因为结构体中就引用了结构类型名,而此时还没定义类型名。
可以改为如下:
typedef struct s_ref_t{   //这里是结构体类型定义和结构体标签
 int a;
 struct s_ref_t *b;  //注意这句与上面相同位置的区别,使用了标签
 char c;
}s_ref ;
这里将运行良好。

以上转自:http://www.cnblogs.com/renyuan/archive/2012/11/30/2796792.html.

下面有个测试小程序:(基本上涵盖了所有的情况)帮助你更好的理解结构体和联合体占用的内存大小.

  1. #include <stdio.h>
  2. void main()
  3. {
  4. typedef struct{
  5. int a;
  6. short b;
  7. }struct_1;
  8. typedef struct_1* struct_0;
  9. typedef struct{
  10. int a;
  11. char b;
  12. struct_1 s1;
  13. }struct_2;
  14. typedef struct{
  15. int a;
  16. char b;
  17. long int c;
  18. struct_1 s1;
  19. }struct_3;
  20. typedef struct{
  21. int a;
  22. char b;
  23. struct_1 * s1;
  24. }struct_4;
  25. typedef struct{
  26. struct_1 * s1;
  27. }struct_5;
  28. typedef struct{
  29. struct_0 s1;
  30. }struct_6;
  31. union union_0{
  32. int a;
  33. char b[30];
  34. long int c;
  35. }union_1;
  36. union test_a{
  37. int a;
  38. double b;
  39. };
  40. typedef struct{
  41. int a;
  42. short b;
  43. union union_0 *c;
  44. }struct_7;
  45. typedef struct{
  46. int a;
  47. struct struct_1* b;
  48. }struct_8;
  49. union test{
  50. long int a;
  51. char c[10];
  52. };
  53. typedef struct{
  54. char b[7];
  55. }struct_size7;
  56. typedef struct{
  57. char a[9];
  58. }struct_size9;
  59. union test_3{
  60. struct_size7 c;
  61. struct_size9 d;
  62. };
  63. typedef struct{
  64. int a;
  65. char b[10];
  66. }struct_12;
  67. union test_11{
  68. int a;
  69. char b[10];
  70. };
  71. union test_3 union_7add9;
  72. union test test_1;
  73. union test_a test_2;
  74. union test_11 test_12;
  75. printf("struct_1 size = %d\n",sizeof(struct_1));
  76. printf("struct_2 size = %d\n",sizeof(struct_2));
  77. printf("struct_3 size = %d\n",sizeof(struct_3));
  78. printf("struct_4 size = %d\n",sizeof(struct_4));
  79. printf("struct_5 size = %d\n",sizeof(struct_5));
  80. printf("struct_6 size = %d\n",sizeof(struct_6));
  81. printf("union_1 size = %d\n",sizeof(union_1));
  82. printf("struct_7 size = %d\n",sizeof(struct_7));
  83. printf("struct_8 size = %d\n",sizeof(struct_8));
  84. printf("test_1 size = %d\n",sizeof(test_1));
  85. printf("test_2 size = %d\n",sizeof(test_2));
  86. printf("struct_size7 = %d\n",sizeof(struct_size7));
  87. printf("struct_size9 = %d\n",sizeof(struct_size9));
  88. printf("union_7add9 = %d\n",sizeof(union_7add9));
  89. printf("struct_12 = %d\n",sizeof(struct_12));
  90. printf("test_12 = %d\n",sizeof(test_12));
  91. }


输出结果:

  1. struct_1 size = 8
  2. struct_2 size = 16
  3. struct_3 size = 24
  4. struct_4 size = 16
  5. struct_5 size = 8
  6. struct_6 size = 8
  7. union_1  size = 32
  8. struct_7 size = 16
  9. struct_8 size = 16
  10. test_1 size = 16
  11. test_2 size = 8
  12. struct_size7 = 7
  13. struct_size9 = 9
  14. union_7add9 = 9
  15. struct_12 = 16
  16. test_12 = 12

(0)

相关推荐