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

一、结构体

1.1什么是结构体

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

1.2结构体的声明

      结构的声明必须包含它的所有成员。它的完全声明如下:

struct 标签
{
    member-list;  //成员列表
}variable-list;   //变量列表
  • 成员列表可以是几种基本数据类型,也可以是结构体类型
  • 注意结构体花括号后面的分号不能丢

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

//第一种  
struct Example
{
    int a;
    char b;
    float c;
};

struct Example x;

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

//第二种  
struct Example
{
    int a;
    char b;
    float c;
}y;

struct Example exp;

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

//第三种 
struct 
{
    int a;
    char b;
    float c;
}exp;

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

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

struct B;

struct A 
{
    struct B;
}

struct B
{
    struct A;
}

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

1.3结构体的初始化

      结构体的初始化与数组的初始化很相似。在一个花括号内部,根据结构成员列表的顺序,用逗号分隔各个成员。下面介绍一种结构体的嵌套初始化:

struc Point
{
    int x;
    int y;
}

struct  Node 
{
    int a;
    short b[3];
    struct Point c;
}x = {
        3,
        {1, 2, 3},
        
    }

1.4结构体需要注意的几点

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

//代码块一
struct 
{
    int a;
    char b;
    float c;
}y[20], *z;
//代码块二
struct Example
{
    int a;
    char b;
    float c;
}

struct Example y[20], *z;

      代码块一中在结构体声明的同时创建了y和z,其中y是一个数组,包含了20个结构,z是一个指针,指向该类型的结构。两个声明被编译器当作两种截然不同的类型,即使y和z的成员列表完全相同。

     代码块二中的y和z是使用标签Example来创建的变量,注意前面要加struct,说明它是结构体类型。此处的y和z是同一种类型的结构变量。

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

typedef struct 
{
    int a;
    char b;
    float c;
}Example;

      这里的Example不是结构体标签,而是一个类型名,所以可以利用以上声明,可以如下:

Example y[20], *z;

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

typedef struct example
{
    int a;
    char b;
    float c;
}Example;

Example y[20], *z;

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

#define Example struct example
Example
{
    int a;
    char b;
    float c;
};
Example y[20], *z;

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

1.5位段

1.5.1位段的声明

      位段的声明和结构类似,但它的成员是一个或多个位的字段。这些不同长度的字段实际上存储于一个或多个整型变量中。

      声明位段需要注意两点:

      (1)位段成员必须声明为int,signed int 或unsigned int类型。

      (2)再成员名的后面是一个冒号和一个整数,这个整数指定该位段所占用的的数目。

1.5.2位段的优缺点

      优点:

      (1)它能够把长度为奇数的数据包装在一起,节省存储空间。当程序中使用成千上万的这类结构时,这种节省办法就显得非常重要。

      (2)它可以很方便的访问一个整型值的部分内容。

      缺点:

      位段的可移植性很差。

      下面引自《C和指针》中对位段可移植性方面的介绍:

      注重可移植性的程序应该尽量避免使用位段。由于下面这些与实现有关的依赖性,位段在不同的系统中可能有不同的结果。

      1.int位段被当作有符号数还是无符号数。

      2.位段中位的最大数目。许多编译器把位段的成员的长度限制在一个整型值的长度之内,所以一个能够运行于32位整数的机器上的位段声明可能在16位整数的机器上无法运行。

      3.位段中的成员在内存中是从左向右分配的还是从右向左分配的。

      4.当一个声明指定了两个位段,第2个位段比较大,无法容纳于第1个位段剩余的位时,编译器有可能把第2个位段放在内存的下一个字,也可能直接放在第1个位段后面,从而在两个内存位置的边界上形成重叠。

二、联合

2.1联合的声明

      联合的声明和结构类似,如果你想在不同的时间把不同的信息存储在同一个位置时,就可以考虑使用联合。联合的所有成员引用的是内存中相同的位置,下面的代码就是一个联合声明的例子

union
{
    float f;
    int i;
}fi;

2.2联合的初始化

      联合成员可以被初始化,但这个初始值必须是第1个成员的类型,而且它必须位于一对花括号里面。比如下面代码

union
{
    int a;
    float b;
    char c[4]
}x = { 5 };  //把x.a初始化为5,我们不能把这个类量初始化为一个浮点数或字符值

2.3联合的存储

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

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

3.1对齐原因

      1.平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

      性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因再与,为了访问未对齐的内存,处理器需要作两次内存访问,而对于对齐的内存访问仅需要一次访问。

3.2对齐规则

      每个特定平台上的编译器都有自己的默认"对齐系数"(也叫对齐模数)。程序员可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数,其中的n就是你要指定的"对齐系数"。

      (1)数据成员对齐规则:结构(或联合)的数据成员,第一个放在offset为0的地方,以后每个数据成员存储的起始位置要按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个的整数倍开始

      请读者仔细查看下面两段代码,char与#pragma pack(2)相比,char只有1个字节大小,自定义对齐数为2,1<2,故该结构体的大小最终为2; 代码块二中,变量d与#pragma pack(2)相比,变量d的类型为int所占4字节大小,自定义对齐数为2,2<4,所以d的存储位置是从2位置开始,之后存储位置从6开始,变量e的类型为double,显然2小于double类型,在32位的程序中,代码块二最终的结构体大小为14。

//代码块一
#pragma pack(2) //自定义的对齐数为2
typedef struct student
{
	char a;
	char b;
}stu;
//代码块二
#pragma pack(2)
typedef struct student
{
	char c;
	int d;
	double e;
}stu;

      (2)结构体作为成员:如果结构体中包含有结构体成员时,子结构体成员自身按照规则一对齐,再与结构体本身的成员按照规则一对齐。

      请注意以下代码,我把#pragma pack(2)定义在stu的后面,此时stu本身是按照与编译器的默认对齐数(我用的是visual studio2017,默认对齐数为4)进行对齐的,计算结果最终test的大小为 22,如果把#pragma pack(2)定义在stu的前面,最终test的大小为 20。

typedef struct student
{
	char a;
	int c;
	double e;
}stu;

#pragma pack(2)
typedef struct test
{
	char b;
	
	stu s;
	int c;

}test;

      (3)收尾工作:结构体的总大小,也就是sizeof的结果,.必须是其内部最大成员的"最宽基本类型成员"的整数倍.不足的要补齐.(基本类型不包括struct/class/uinon)。

      (4)联合的大小:sizeof(union),以联合里面size最大元素为union的size,因为在某一时刻,联合只有一个成员真正存储于该地址。

猜你喜欢

转载自blog.csdn.net/William_Tuo/article/details/81147860
今日推荐