结构体(Struct)、联合体(Union)和位域

6,031 阅读5分钟

结构体(Sturct)

C数组允许定义可存储相同数据类型数据项的变量,结构是C编程中另一种用户自定义的可用的数据类型,它允许您存储不同类型的数据项。

定义结构体

struct tag { 
    member-list
    member-list 
    member-list  
    ...
} variable-list ;

tag是结构体标签。 member-list是标准的变量定义,比如int i;或者float f,或者其他有效的定义变量。 variable-list 结构变量,定义在结构的末尾,最后一个分号之前,您可以指定一个或多个结构变量。 下面是声明 Book 结构的方式:

struct Books
{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
} book;

在一般情况下,tag、member-list、variable-list 这 3 部分至少要出现 2 个。

//此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c
//同时又声明了结构体变量s1
//这个结构体并没有标明其标签
struct 
{
    int a;
    char b;
    double c;
} s1;
 
//此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c
//结构体的标签被命名为SIMPLE,没有声明变量
struct SIMPLE
{
    int a;
    char b;
    double c;
};
//用SIMPLE标签的结构体,另外声明了变量t1、t2、t3
struct SIMPLE t1, t2[20], *t3;
 
//也可以用typedef创建新类型
typedef struct
{
    int a;
    char b;
    double c; 
} Simple2;
//现在可以用Simple2作为类型声明新的结构体变量
Simple2 u1, u2[20], *u3;

在上面的声明中,第一个和第二声明被编译器当作两个完全不同的类型,即使他们的成员列表是一样的,如果令 t3=&s1,则是非法的。

结构体的成员可以包含其他结构体,也可以包含指向自己结构体类型的指针,而通常这种指针的应用是为了实现一些更高级的数据结构如链表和树等。

//此结构体的声明包含了其他的结构体
struct COMPLEX
{
    char string[100];
    struct SIMPLE a;
};
 
//此结构体的声明包含了指向自己类型的指针
struct NODE
{
    char string[100];
    struct NODE *next_node;
};

如果两个结构体互相包含,则需要对其中一个结构体进行不完整声明,如下所示:

struct B;    //对结构体B进行不完整声明
 
//结构体A中包含指向结构体B的指针
struct A
{
    struct B *partner;
    //other members;
};
 
//结构体B中包含指向结构体A的指针,在A声明完后,B也随之进行声明
struct B
{
    struct A *partner;
    //other members;
};

struct的大小遵循字节对齐。

struct对齐规则

位域

位域定义与结构定义相仿,其形式为:

struct 位域结构名 
{

 位域列表

};

其中位域列表的形式为:

类型说明符 位域名: 位域长度(占多少位)

例如:

struct bs{
    int a:8;
    int b:2;
    int c:6;
}data;

说明 data 为 bs 变量,共占两个字节。其中位域 a 占 8 位,位域 b 占 2 位,位域 c 占 6 位。

对于位域的定义尚有以下几点说明:

  • int : 0表示下面出现的类型不能够再往里面填充。
struct area
{
  int a:3;   
  int:0;     //告诉下面,20不能够放到a的那块存储区域去了
  int c:20;
};

结构体大小为4+4=8;如果没有int:0,大小为4。

  • int : x 表示占用几个位,占用部分不可用,但是接下来的位可继续往上面填充。
struct area
{
  int a:3;   
  int:4;     //表示a和c中间有4位不可用。
  int c:20;
};

该结构体大小为4。

如果int:4改为int:10,那么该结构体大小会变为8。

  • 位域不能跨字节,位域需要跟结构体最长类型对齐,前面的类型应自动对齐,然后再进行填充。
struct area1
{
  char a:6;   //向int对齐,扩展为32位
  char b:3;
  int c:22;
};

结构体大小 :8

struct area2
{
  char a:6;   //向int对齐,扩展为32位
  char b:3;
  int c:21;
};

结构体大小 :4

位域压缩的大致规则

  1. 如果相邻位域字段的类型相同,且其位宽之和小于这个类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止;
  2. 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍;
  3. 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方式,Dev-C++采取压缩方式;
  4. 如果位域字段之间穿插着非位域字段,则不进行压缩;
  5. 整个结构体的总大小为最宽基本类型成员大小的整数倍。

联合体/共用体(Union)

联合体(union)与结构体(struct)有一些相似之处。但两者有本质上的不同。在结构体中,各成员有各自的内存空间, 一个结构变量的总长度是各成员长度之和。而在联合体中,各成员共享一段内存空间, 一个联合变量的长度等于各成员中最长的长度。

  • 联合体是一个结构;
  • 它的所有成员相对于基地址的偏移量都为0;
  • 此结构空间要大到足够容纳最"宽"的成员;
  • 其对齐方式要适合其中所有的成员;

联合体的内存对齐

联合体所占空间需要满足的条件:

  1. 大小足够容纳最宽的成员;
  2. 大小能被其包含的所有基本数据类型的大小所整除。
union U
{
    char s[9]; // 9
    int n;     // 4
    double d;  // 8
};

联合体的大小为 16。

参考:

C结构体

union联合体与内存对齐

浅谈C语言中的联合体