结构体概述
1 结构体使用时先定义结构体类型,再用类型定义变量
2 结构体变量中的元素访问方式只有一种,用句点.或者箭头->。
3 结构体的对齐访问
访问结构体时需要对齐访问,主要是配合硬件,也就是说硬件硬件本身有物理上的限制,因此对齐排布和访问可以提高访问效率,对齐访问牺牲了内存,但换取了速度性能。
对齐访问的规则和运算
(1)当编译器将结构体设置为4字节对齐时,结构体整体必须从4字节对齐处存放,结构体对齐后的大小必须为4的倍数。如果编译器设置为8字节对齐,这里的4就是8.
(2)结构体中每个元素本身也必须对齐存放;
(3)编译器在考虑以上两点情况下,实现以最少内存来开辟结构体空间。
手动对齐
需要以#pragma pack(n)开头,以#pragma pack()结尾,定义一个区间,这个区间内的对齐参数是n。
GCC推荐的对齐指令
_ attribute _((packed)) 和 _ attribute _((aligned(n)))
直接放在类型定义的后面,那么该类型就以指定的方式进行对齐,packed的作用就是取消对齐,aligned(n)表示对齐方式。
offsetof宏与container_of宏
offsetof宏
计算结构体中某个元素相对结构首字节地址的偏移量,其实质是通过编译器来计算的。
container_of宏
作用:知道一个结构体中某个成员的指针,反推这个结构体变量的指针。
typeof关键字的作用:通过typeof(a)由变量a 得到a的类型,由变量名得到变量的数据类型。
工作原理:先用typeof得到member成员的类型,将member成员的指针转成自己类型的指针,然后用这个指针将去该成员相对于整个结构体变量的偏移量(偏移量用offsetof宏得到),之后得到就是整个结构体变量的首地址,再把这个地址强制类型转换为type* 即可。
共用体(union)
(1)共用体和结构体在类型声明、变量定义和使用方法相似;
(2)结构体中的成员彼此独立,分布在内存的不同单元中;共用体中的各个成员是一体的,使用的是同一内存单元;
(3)共用体的sizeof测试的大小实际是共用体中各个元素里面占用内存最大的元素大小;
(4)共用体不存在内存对齐,只有一个内存空间,从同一地址(就是整个共用体占有的内存空间的首地址)开始。
(5)主要对同内存单元进行多种不同规则解析
大小端模式
大端模式:高字节对应地地址;
小端模式:高字节对应高地址;
理论上按照大小端模式都可以,但是要求存储和读取时必须按照同样的大小端模式来进行。否则会出错。
测试当前机器的大小端模式
#include<stdio.h>
//共用体中很重要的一点a和b都是从u1的地地址开始的
//假设u1所在的4字节地址分别是:0、1、2、3
//那么a自然就是0、1、2、3;b所在的地址是0而不是3
union myunion
{
int a;
char b;
};
//如果小端模式则返回1,大端模式则返回0
int is_little_endian(void)
{
union myunion u1;
u1.a = 1;
return u1.b;
//int a = 1;
//char b = * ((char *)(&a));
//return b;
}
int main (void)
{
int i = is_little_endian();
if (1 == i)
{
printf("小端模式\n");
}
else
{
printf("大端模式\n");
}
return 0;
}
枚举(enum)
使用枚举其实就是对1、0这些数字进行符号化编码,这样的好处是编程时可以不用看数组而直接看符号。枚举是将多个有关联的符号封装在一个枚举中,而宏定义是完全分散的,当我们定义是一个有限集合时,适合用枚举。
在一个文件汇总不能有两个或者两个以上的enum被typedef成相同的别名;
enum成员的访问方式是成员名,不能有重名的枚举成员;但是两个#define宏定义是可以重名的,该宏名真正的值取决于最后一次定义的值。