这篇文章主要是简单分析一下结构体和位段的知识。
在c和指针这本书中我们可以了解到数据经常以成组的形式存在。例如,雇主必须明了雇员的姓名,年龄和工资。如果这些能够储存在一起,访问起来会简单一些。但是如果这些值得类型不同(就像现在这种情况),他们无法储存于同一个数组中。在c中,我们使用结构可以把不同类型的值储存在一起。
1.结构体的声明和定义:
在声明结构时,我们必须列出它包含的所有成员。这个列表包括每个成员的类型和名字。下面我们写一个结构体的声明,它是在声明结构体时定义变量:
struct Simple
{
int a;
char b;
float c;
}x;
这个声明定义了一个名叫x的变量,它包含了三个成员:一个整数,一个字符和一个字符数。
还可以在主函数中定义结构体变量:
struct Simple
{
int a;
char b;
float c;
};
int main()
{
struct Simple x;//定义一个结构体变量
return 0;
}
2.结构体的初始化:
结构体的初始化方式和数组的初始化方式很相似。结构中如果包含数组或结构成员,其初始化方式类似于多维数组的初始化。
可以分为俩种,一种是对在声明结构体时定义的结构体变量初始化:
struct Student
{
char name[20];
int age;
float score;
}stu = {
"cuihua",
18,
90.0
};
另一种是对在主函数里定义的结构体变量初始化:
struct Student
{
char name[20];
int age;
float score;
};
int main()
{
struct Student stu = { "cuihua", 18, 90.0 };//对结构体变量进行初始化
return 0;
}
3.结构体的typedef
声明结构时可以使用的另一种良好技巧是用typedef创建一种新的类型,如下面的例子所示:
#include<stdio.h>
typedef struct
{
char name[20];
int age;
float score;
}Student;
int main()
{
Student stu = { "cuihua", 18, 90.0 };//在主函数里面定义结构体变量
printf("%d", stu.age);
return 0;
}
通过上面代码我们可以发现加上typedef关键词以后我们在主函数里面定义结构体变量比之前的方式简洁了很多,现在的Student是一个结构体类型名而不是一个结构体标签,所以我们代码后面定义变量就变得简单方便了很多。这是上面代码运行结果:
接下来我们分析一下结构体在内存中的储存规则,首先我们用下面代码测试一下它的内存大小:
#include<stdio.h>
typedef struct
{
char name[20];
int age;
float score;
}Student;
int main()
{
Student stu = { "cuihua", 18, 90.0 };
printf("%d", sizeof(Student));
return 0;
}
让我们来看一下上面这个代码的运行结果:
为什么是28呢?这就要先说一下结构体的内存对齐规则了,有以下三点
1:结构的第一个成员永远都放在结构的0偏移处。
2:从第二个成员开始,都要对齐到某个对齐数的整数倍处。(对齐数为结构成员自身大小和默认对齐数的较小值)(默认对齐数与操作系统有关,在vs操作环境下是8,linux操作系统下是4)(用# Pragma pack(n) 可以 )
3:结构的总大小必须是最大对齐数的整数倍。
知道了内存对齐规则之后我们就可以分析一下上面代码的结果为什么是28了。
typedef struct
{
char name[20];//0-19 根据内存对齐的第一条规则可知第一个成员是放在0偏移处的,因为是20个char类型的占20个字节所以从0偏移处到19
//偏移处
int age;//20-23 对齐数为4,20偏移处正好是4的整数倍,所以不需要浪费空间,1个int类型的占4个字节所以从20偏移处到23偏移处
float score;//24-27 对齐数为4,24偏移处是4的整数倍,所以同样不需要浪费空间,1个float类型占4个字节所以从24偏移处到27偏移处
//三个成员共偏移了28个大小,最大对齐数为4,所以结构体的总大小为28
}Student;
那么那么为什么存在内存对齐规则呢,这里主要有俩个原因1 : 平台原因:不是所有硬件平台都能访问任意地址上任意数据的;某些硬件平台只能在某些地址处取得某些特定类型的数据,否则抛出硬件异常;
2:性能原因:数据结构应该尽可能的在自然边界上对齐。原因是为了访问未对齐的内存,处理器需要作俩次访问,二对齐的内存访问仅需一次访问。
最后我们分析一下位段。
位段的成员声明必须声明为int、signed、或unsigned int类型,在成员名的后面是一个冒号和一个整数,这个整数指定该位段所占用的位的数目。
下面是一个结构体实现位段的例子:
struct Simple
{
unsigned a : 7;
unsigned b : 6;
int c : 3;
};
下面说说位段的跨平台问题,位段为什么不能跨平台呢:
当一个声明指定了俩个位段,第二个位段比较大,无法容纳第一个位段剩余的位时,编译器有可能把第二个位段放在内存的下一个字,也有可能直接放在第一个位段后面,从而在俩个内存位置的边界上形成重叠。还有就是位段中的成员在内存中是从左向右分配的还是从右向左分配的。因此位段是不能跨平台的。(这个也是位段计算大小的方法,计算位段大小也是不能跨平台的,在vc操作环境下,第二个位段较大无法容纳第一个位段剩余的位时编译器会把第二个位段放在内存的下一个字,并且在vc操作环境下位段中的成员是从右向左分配的)