1、结构体可以认为是数组发展而来的。数组和结构体是最简单的数据结构。
2、数组特点(缺陷):
(1)定义时必须明确给出大小,且这个大小在后面不能再更改
(2)数组要求所有元素的类型必须一致
更复杂的数据结构就是为了解决数组的这两个缺陷。
3、结构体是用来解决数组的第二个缺陷,结构体中元素类型可以不相同。结构体完全可以取代数组,只是在数组可用的范围内数组比结构体更简单。
C语言中有2中类型:原生类型和自定义类型。结构体就是其中一种自定义类型。
1、结构体定义时需要先定义结构体类型,然后再用类型来定义变量。
2、在定义结构体类型的同时定义结构体变量。
// 定义结构体类型
struct student
{
char name[20];
int age;
};
// 在函数中使用结构体类型定义变量
struct student p2;
// 定义类型的同时定义变量
struct people
{
char name[20];
int age;
}p1;
// 在函数中直接使用变量
p1.age = 23;
// 将类型struct people结构体重命名为p1,这里的p1就不是变量了
typedef struct people
{
char name[20];
int age;
}p1;
1、数组中元素的访问方式:表面上有2中方式(数组下标方式和指针方式),访问的实质都是通过指针访问的
2、结构体变量中的元素访问方式只有一种,用 ‘.’ 或者 ’->‘ 的方式访问。这两种方式访问实质是一样的(都是用指针访问的),只是C语言规定用结构体变量来访问用 ‘.’ 用结构体变量的指针来访问元素用 ’->‘ (高级语言中已经不区分都用.)
// 结构体定义
struct test
{
int a; // 4
double b; // 8
char c;
};
// 结构体访问
struct test s1;
s1.a = 12; // int *p = (int *)&s1; *p = 12;
s1.b = 3.4; // double *p = (double *)((int)&s + 4 ); *p = 3.4;
s1.c = 'a'; // char *p = (char *)((int)&s + 12 ); *p ='a';
带着问题了解下结构体对齐访问与非对齐访问:
1、结构体为什么要对齐访问?
答: 结构体元素对齐访问主要原因是为了配合硬件,也就是说硬件本身有物理上的限制,如果对齐排布和访问会提高效率
内存本身是一个物理器件(DDR内存芯片,SOC上DDR控制器),本身有一定的局限性:如果内存每次访问时按照4字节对齐访问,效率是最高的。
2、结构体对齐访问与非对齐访问哪个好?
答: 结构体对齐访问与非对齐访问需要看程序员注重的是内存空间还是速度性能。本质上两者并无好坏之分。
对齐访问:牺牲了内存空间,换取速度性能
非对齐访问:牺牲了访问速度性能,换取了内存空间的完全利用。
3、结构体在内存中究竟是怎样一个访问过程
答: 结构体中元素的访问本质上还是指针方式,结合这个元素在结构体的偏移量和这个元素的类型来进行访问。
4、应如何考虑结构体对齐问题?
答: 结构体要考虑元素的对齐访问,每个元素实际占用的字节数和自己本身的类型所占的字节数不一定完全一样。
5、在访问结构体成员时,需要考虑元素对齐问题吗?
答: 用’.'方式来访问结构体中元素时,我们是不用考虑结构体的元素对齐。因为编译器会将这些元素对齐并知道每个元素在对齐后占多少字节。
分析结构体对齐关键:
1、结构体整体本身必须放在4字节对齐处,结构体对齐后的大小必须是4的倍数(编译器设置为4字节对齐时)。
2、结构体中每个元素本身都必须对其存放,而每个元素本身都有自己的对齐规则。
// 分析:
// 1、整个结构体变量4字节对齐是由编译器保证的
// 2、第一元素a,a的开始地址就是整个结构体的开始地址,所以自然是4字节对齐的,但是a的结束地址要由下一元素决定。
// 3、第二元素b,因为上个元素a本身占4字节,本身就是对齐的。所以留给b的开始地址是4字节地址,所以直接放。(b放的位置决定a一共占4字节,不需要填充)
// b的开始地址确定,结束地址要根据下一个元素来决定
// 4、第三元素c,short类型需要2字节对齐(short类型元素必须放在类似0/2/4/6.....这样的地址处),因此不能紧挨着b放,需要在b之后添加1字节的填充,然后再开始放c
// 5、整个结构体的所有元素都对齐存放后,整个结构体大小还要是4的倍数
struct s
{
int a;
char b;
short c;
};
gcc支持但不推荐的对齐指令:
#pragma pack()
#pragma pack(n) (n = 1/2/3/4)
1、#pragma是用来指挥编译器,或者说设置编译器的对齐方式的。编译器默认对齐方式是4。现在不希望编译器对齐方式的4字节对齐,希望是1字节对齐。
这时gcc提供两种对齐命令:#pragma pack(),这种就是设置成1字节对齐;#pragma pack(1),这个括号中的数字是希望多少字节对齐
2、以 #pragma pack(n) 开头,#pragma pack() 结尾,这区间内自定义字节对齐生效。
#pragma pack的方式在很多C环境下都是支持的,gcc也可以用但是不推荐。
gcc推荐的对齐命令:
__ attribute__((packed));
__ attribute__((aligned(n)));
1、__ attribute__((packed)); 使用时直接放在要进行内存对齐的类型定义后面,起作用的范围只有加了这个东西的这一个类型。packed的作用就是取消对齐访问。
2、__ attribute__((aligned(n))); 使用时直接放在要进行内存对齐的类型定义后面,起作用的范围只有加了这个东西的这一个类型。作用是让整个结构体变量整体进行n字节对齐。(作用针对的是整体的结构体起效果,对结构体内元素不起效果)