【C语言】结构体、联合,内存对齐规则总结

小灰灰 2022-05-19 06:23 290阅读 0赞

一、结构体

1.1什么是结构体

  1. C语言中,结构体是一种数据结构,是C提供的聚合类型(C提供了两种聚合类型:数组和结构)的一种。**结构体与数组的区别是:数组是相同类型的集合,而结构体可能具有不同的类型。** 结构体也可以被声明为变量,数组或者指针等,用以实现较复杂的数据结构,它的成员可通过成员名来访问。

1.2结构体的声明

  1. 结构的声明必须包含它的所有成员。它的完全声明如下:
  2. struct 标签
  3. {
  4. member-list; //成员列表
  5. }variable-list; //变量列表
  • 成员列表可以是几种基本数据类型,也可以是结构体类型
  • 注意结构体花括号后面的分号不能丢

    结构体的声明,有完全声明,还可以不完全的声明它。下面介绍几种结构体的声明

    //第一种
    struct Example
    {

    1. int a;
    2. char b;
    3. float c;

    };

    struct Example x;

    先声明结构体类型,然后用这个类型创建了一个x变量。

    //第二种
    struct Example
    {

    1. int a;
    2. char b;
    3. float c;

    }y;

    struct Example exp;

    在声明结构体类型的同时创建了一个y变量,同时还可用结构体类型再创建其它变量。

    //第三种
    struct
    {

    1. int a;
    2. char b;
    3. float c;

    }exp;

    这个声明创建了一个名叫exp的变量,之后再不能利用该结构体类型创建变量。

    下面介绍一种结构体的不完整声明:

    struct B;

    struct A
    {

    1. struct B;

    }

    struct B
    {

    1. struct A;

    }

    上面这种声明在A的成员列表中需要标签B的不完整声明,而在A声明之后,B的成员列表就可以直接使用A。这个类似于函数的声明和调用。

1.3结构体的初始化

  1. 结构体的初始化与数组的初始化很相似。在一个花括号内部,根据结构成员列表的顺序,用逗号分隔各个成员。下面介绍一种结构体的嵌套初始化:
  2. struc Point
  3. {
  4. int x;
  5. int y;
  6. }
  7. struct Node
  8. {
  9. int a;
  10. short b[3];
  11. struct Point c;
  12. }x = {
  13. 3,
  14. {1, 2, 3},
  15. }

1.4结构体需要注意的几点

(1)看下面两个代码块:

  1. //代码块一
  2. struct
  3. {
  4. int a;
  5. char b;
  6. float c;
  7. }y[20], *z;
  8. //代码块二
  9. struct Example
  10. {
  11. int a;
  12. char b;
  13. float c;
  14. }
  15. struct Example y[20], *z;
  16. 代码块一中在结构体声明的同时创建了yz,其中y是一个数组,包含了20个结构,z是一个指针,指向该类型的结构。两个声明被编译器当作两种截然不同的类型,即使yz的成员列表完全相同。
  17. 代码块二中的yz是使用标签Example来创建的变量,注意前面要加struct,说明它是结构体类型。此处的yz是同一种类型的结构变量。

(2)在C中利用typedef创建类型时,struct可以省略

  1. typedef struct
  2. {
  3. int a;
  4. char b;
  5. float c;
  6. }Example;
  7. 这里的Example不是结构体标签,而是一个类型名,所以可以利用以上声明,可以如下:
  8. Example y[20], *z;

(3)还可以利用typedef为struct example取一个别名”Example”,之后也可以省略struct;

  1. typedef struct example
  2. {
  3. int a;
  4. char b;
  5. float c;
  6. }Example;
  7. Example y[20], *z;

(4)在某些情况下还可以使用#define来实现更简化的结构体定义与变量的定义,但可能会牺牲部分可读性。

  1. #define Example struct example
  2. Example
  3. {
  4. int a;
  5. char b;
  6. float c;
  7. };
  8. Example y[20], *z;

typedef和#define用法不同,甚至可以结合起来灵活使用,使用时一定要注意两者的不同之处。

1.5位段

1.5.1位段的声明

  1. 位段的声明和结构类似,但它的成员是一个或多个位的字段。这些不同长度的字段实际上存储于一个或多个整型变量中。
  2. 声明位段需要注意两点:
  3. 1)位段成员必须声明为int,signed int unsigned int类型。
  4. 2)再成员名的后面是一个冒号和一个整数,这个整数指定该位段所占用的**位**的数目。

1.5.2位段的优缺点

  1. 优点:
  2. 1)它能够把长度为奇数的数据包装在一起,节省存储空间。当程序中使用成千上万的这类结构时,这种节省办法就显得非常重要。
  3. 2)它可以很方便的访问一个整型值的部分内容。
  4. 缺点:
  5. 位段的可移植性很差。
  6. 下面引自《C和指针》中对位段可移植性方面的介绍:
  7. 注重可移植性的程序应该尽量避免使用位段。由于下面这些与实现有关的依赖性,位段在不同的系统中可能有不同的结果。
  8. 1.int位段被当作有符号数还是无符号数。
  9. 2.位段中位的最大数目。许多编译器把位段的成员的长度限制在一个整型值的长度之内,所以一个能够运行于32位整数的机器上的位段声明可能在16位整数的机器上无法运行。
  10. 3.位段中的成员在内存中是从左向右分配的还是从右向左分配的。
  11. 4.当一个声明指定了两个位段,第2个位段比较大,无法容纳于第1个位段剩余的位时,编译器有可能把第2个位段放在内存的下一个字,也可能直接放在第1个位段后面,从而在两个内存位置的边界上形成重叠。

二、联合

2.1联合的声明

  1. 联合的声明和结构类似,如果你想在不同的时间把不同的信息存储在同一个位置时,就可以考虑使用联合。**联合的所有成员引用的是内存中相同的位置**,下面的代码就是一个联合声明的例子
  2. union
  3. {
  4. float f;
  5. int i;
  6. }fi;

2.2联合的初始化

  1. 联合成员可以被初始化,但这个初始值必须是第1个成员的类型,而且它必须位于一对花括号里面。比如下面代码
  2. union
  3. {
  4. int a;
  5. float b;
  6. char c[4]
  7. }x = { 5 }; //把x.a初始化为5,我们不能把这个类量初始化为一个浮点数或字符值

2.3联合的存储

  1. 分配给联合的内存数量取决于它的最长成员的长度。当联合成员的长度相差悬殊,当存储长度较短的成员时,浪费的空间是相当可观的。在这种情况下,更好的方法是在联合中存储指向不同成员的指针而不是直接存储成员本身。所有指针的长度都是相同的,这样就解决了内存浪费的问题。

三、结构体和联合的内存对齐问题

3.1对齐原因

  1. 1.平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
  2. 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因再与,为了访问未对齐的内存,处理器需要作两次内存访问,而对于对齐的内存访问仅需要一次访问。

3.2对齐规则

  1. 每个特定平台上的编译器都有自己的默认"对齐系数"(也叫对齐模数)。程序员可以通过预编译命令\#pragma pack(n),n=1,2,4,8,16来改变这一系数,其中的n就是你要指定的"对齐系数"
  2. 1)数据成员对齐规则:结构(或联合)的数据成员,第一个放在offset0的地方,以后每个数据成员存储的起始位置要**按照\#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个的整数倍开始**。
  3. 请读者仔细查看下面两段代码,char\#pragma pack(2)相比,char只有1个字节大小,自定义对齐数为21<2,故该结构体的大小最终为2 代码块二中,变量d\#pragma pack(2)相比,变量d的类型为int所占4字节大小,自定义对齐数为22<4,所以d的存储位置是从2位置开始,之后存储位置从6开始,变量e的类型为double,显然2小于double类型,在32位的程序中,代码块二最终的结构体大小为14
  4. //代码块一
  5. #pragma pack(2) //自定义的对齐数为2
  6. typedef struct student
  7. {
  8. char a;
  9. char b;
  10. }stu;
  11. //代码块二
  12. #pragma pack(2)
  13. typedef struct student
  14. {
  15. char c;
  16. int d;
  17. double e;
  18. }stu;
  19. 2)结构体作为成员:如果结构体中包含有结构体成员时,子结构体成员自身按照规则一对齐,再与结构体本身的成员按照规则一对齐。
  20. 请注意以下代码,我**把\#pragma pack(2)定义在stu的后面**,此时stu本身是按照与编译器的默认对齐数(我用的是visual studio2017,默认对齐数为4)进行对齐的,计算结果最终test的大小为 22,如果**把\#pragma pack(2)定义在stu的前面**,最终test的大小为 20
  21. typedef struct student
  22. {
  23. char a;
  24. int c;
  25. double e;
  26. }stu;
  27. #pragma pack(2)
  28. typedef struct test
  29. {
  30. char b;
  31. stu s;
  32. int c;
  33. }test;
  34. 3)收尾工作:结构体的总大小,也就是sizeof的结果,.必须是其内部最大成员的"**最宽基本类型成员**"的整数倍.不足的要补齐.(基本类型不包括struct/class/uinon)。
  35. 4)联合的大小:sizeof(union),以联合里面size最大元素为unionsize,因为在某一时刻,联合只有一个成员真正存储于该地址。

发表评论

表情:
评论列表 (有 0 条评论,290人围观)

还没有评论,来说两句吧...

相关阅读

    相关 C语言结构的字节对齐原则

     为什么要对齐? 现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特 定的