结构体基础

结构体就是一些成员的集合,结构体的每一个成员可以是整型、数组、指针、结构体等不同的类型。

下面是一个简单的结构体结构,包含了类型声明struct Stu、成员、结构体变量s1的声明。

1
2
3
4
5
6
struct Stu {  //类型
//成员
char name[20];
int age;
char sex[10];
}s1; //结构体变量

我们可以像上面那样声明一个结构体变量,也可以像下面这样单独声明。

1
struct Stu s2;

struct是结构体关键字,Stu是结构体标志,两者构成了结构体类型。上面的语句表示为struct Stu类型的结构体声明了一个变量s1。下面是对s1的赋值操作,可以在声明结构体变量的时候直接赋值。

1
struct Stu s1 = { "panghutx",20,"male" };

在声明结构体时,我们可以对结构体不完全声明。

1
2
3
4
5
6
7
8
9
10
struct{
int a;
char b;
double c;
} a;
struct{
int a;
char b;
double c;
} *P;

以上就不完全声明了两个结构体,我们称之为匿名结构体类型。结构体变量a和*p具有相同的成员,但它们是两个完全不同的类型。当我们尝试如下代码时,会出现警告。

1
*p = &a;

警告信息

说到结构体,我们难免提到一个关键字typedef,用于定义新的类型(或类型重命名)。我们在学习链表时可能会看到这样的结构,下面这段代码是对struct Node重命名为Node.而且还在结构体中引用了自己。

1
2
3
4
5
typedef struct Node
{
int data;
Node* next;
}Node;

切记在结构体自引用时不要使用匿名结构体,否则就是在定义新类型的时候引用了新类型,这是错误的。

再看下面的写法,定义了两个新类型,Node*pNode,Node我们已经知道是对struct Node进行重命名,而*pNode是对struct Node*的重命名。

1
2
3
4
5
typedef struct Node
{
int data;
Node* next;
}Node,*pNode;

结构体内存对齐

结构体的大小不是单纯的各元素相加,因为主流计算机使用的是32bit字长的CPU,那么取4个字节数要比1个高效,所以结构体存在内存对齐。每个编译器都有自己的对齐系数,程序员也可以通过预编译命令来改变默认对齐数。

1
#pragma pack(n) //n为修改的对齐系数

对齐规则:

①首个成员放在0ffset(偏移量)为0的位置,其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。

对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。

②结构体总大小为各元素最大对齐数的整数倍。

举个例子,计算下面结构体的大小是多少。

1
2
3
4
5
6
struct S1
{
char c1;
int i;
char c2;
};

假设编译环境默认4字节对齐。

c1是结构体首个元素,直接放到偏移量为0的位置,占1个字节;i自身大小为4字节,默认对齐4字节,因此对齐数就是四字节,将其放到对齐数整数倍的位置,也就是4偏移量的位置。c2自身大小1字节,默认对齐数4,因此对齐数是1,将其放到对齐数整数倍的位置,也就是int的后面。

0~8偏移量,那么该结构体为9个字节,对吗?别忘了规则②,结构体总大小是各元素最大对齐数的整数倍。结构体内最大对齐数的元素是int,对齐数是4,9不是4的整数倍,再开辟3个字节。

综上该结构体大小为12字节。

结构体内存对齐

结构体位段

c语言允许在一个结构体中以位为单位来指定成员长度,利用位段能够节约空间。

1
2
3
4
5
6
7
struct A
{
int _a:2;
int _b:5;
int _c:10;
int _d:30;
};

A就是1个位段,它的大小为8个字节,想知道为什么是8个字节,要知道它的内存分配。

  • 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
  • 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
  • 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段

调试下面代码,我们可以看一下空间是如何开辟的。

1
2
3
4
5
6
7
8
9
10
11
12
struct S
{
char a:3;
char b:4;
char c:5;
char d:4;
};
struct S s = {0};
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;

位段内存分配

我在vs2019环境下调试,和vs2013结果一样。先开辟一字节,从低位开始存数据,存不下时舍弃剩余位,再开辟一字节空间。

以上存储方式只能代表vs环境下,其他环境不确定。要知道,位段的内存分配与内存对齐的实现方式依赖于具体的机器和系统,在不同的平台可能有不同的结果,这导致了位段在本质上是不可移植的。