30.1.C语言之结构体概述
(1)C语言中有两种类型(原生类型和自定义类型),结构体是一种自定义类型。
(2)结构体使用时可以先定义类型,然后使用类型定义结构体变量;也可以在定义结构体类型的同时定义结构体变量。
30.2.从数组到结构体的进化
(1)结构体是从数组发展而来的,数组和结构体都属于数据结构,数组就是最简单的数据结构,结构体比数组更复杂一些,链表、哈希表之类的比结构体又复杂一些,二叉树、图等又更复杂一些。
(2)数组的缺陷:第1个是定义时必须明确给出大小,且以后不能再更改;第2个是数组要求所有的元素的类型必须一致。更复杂的数据结构中就致力于解决数组这2个缺陷。
(3)结构体用来解决数组的第2个缺陷,可以将结构体理解为元素类型可以不相同的数组;结构体完全可以取代数组,只是在数组可用的范围内数组比结构体更简单。
30.3.访问结构体和数组中的元素
(1)数组中元素的访问方式:表面上有2种方式(数组下标方式和指针方式);实质上都是指针方式访问。
(2)结构体中的元素访问方式:只有1种(用.或->的方式来访问);.和->访问结构体元素其实质是一样的,只是C语言规定用结构体变量来访问元素用.号,用结构体变量的指针来访问元素用->号,实际上在高级语言中都用.号来访问。
(3)结构体的访问方式类似数组下标访问的方式,结构体变量的.号或者->号访问元素的实质是用指针来访问的。
30.4.结构体对齐访问的来龙去脉
(1)结构体中元素的访问的本质是用指针方式结合该元素在整个结构体中的偏移量和该元素的类型来进行访问的;因为结构体要考虑元素的对齐访问,则每个元素实际占的字节数和其本身类型所占字节数不一定相等(譬如char c实际占字节数可能是1或2或3或4…);当使用.号的方式来访问结构体元素时,我们不用考虑结构体的元素对齐,编译器会帮我们处理该细节,但因为C语言本身是很底层的语言,在嵌入式开发经常需要从内存角度,以指针方式来处理结构体及其中的元素,则我们需要掌握结构体对齐规则。
(2)结构体中元素对齐访问主要原因是为了配合硬件,即硬件本身有物理上的限制,若对齐排布和访问会提高效率,否则会大大降低效率;内存本身是一个物理器件(DDR内存芯片,SoC上的DDR控制器),本身有一定的局限性,若内存每次访问时按照4字节对齐访问,那么效率是最高的,若不对齐访问效率要低很多;还有很多别的因素和原因,导致我们需要对齐访问,譬如Cache的一些缓存特性,还有其他硬件(譬如MMU、LCD显示器)的内存依赖特性会要求内存对齐访问。
(3)对比对齐访问和不对齐访问:对齐访问牺牲了内存空间,换取了速度性能;而非对齐访问牺牲了访问速度性能,换取了内存空间的完全利用。
30.5.结构体对齐的规则和运算
(1)编译器本身可以设置内存对齐的规则:一般32位编译器默认对齐方式是4字节对齐。
(2)结构体对齐分析要点和关键:结构体整体本身必须安置在4字节对齐处,结构体对齐后的大小必须4的倍数(编译器设置为4字节对齐时,如果编译器设置为8字节对齐,则这里的4是8);结构体中每个元素本身都必须对其存放,而每个元素本身都有自己的对齐规则(见图1);编译器考虑结构体存放时,以满足以上2点要求的最少内存需要的排布来算。
30.6.gcc支持但不推荐的对齐指令
(1)#pragma是用来设置编译器的对齐方式,编译器的默认对齐方式是4,但有时候我们希望是别的对齐方式(譬如希望1字节对齐,也可能希望是8,甚至可能希望128字节对齐)。
(2)我们需要#pragama pack(n)(n=1/2/4/8/128,n的值即表示希望多少字节对齐)开头,以#pragma pack()(设置编译器1字节对齐、设置编译器不对齐访问、取消编译器对齐访问)结尾,定义一个区间表示该区间内的对齐参数就是n。
(4)#pragma pack的方式在很多C环境下都是支持,gcc虽然支持但不建议使用。
30.7.gcc推荐的对齐指令
(1)下划线attribute__((packed))(直接放在要进行内存对齐的类型定义的后面,起作用范围为该类型,packed的作用为取消编译器对齐访问)。
(2)下划线attribute__((aligned(n)))(直接放在要进行内存对齐的类型定义的后面,起作用范围为该类型,它的作用是让整个结构体变量整体进行n字节对齐,注意是结构体变量整体n字节对齐,而不是结构体内各元素也要n字节对齐)。
30.8.参考阅读博客
(1)http://www.cnblogs.com/dolphin0520/archive/2011/09/17/2179466.html
(2)http://blog.csdn.net/sno_guo/article/details/8042332
30.struct_define
/*
* 公司:XXXX
* 作者:Rston
* 博客:http://blog.csdn.net/rston
* GitHub:https://github.com/rston
* 项目:C语言结构体对齐访问
* 功能:演示结构体定义和访问及结构体对齐问题。
*/
#include <stdio.h>
// 定义结构体类型
struct people
{
char name[20];
int age;
};
// 定义结构体类型的同时定义变量
struct student
{
char name[20];
int age;
}s1;
// 使用typedef将类型struct friend重命名为friend
// 共定义了1种类型,有2个名字:struct friend和friend
typedef struct friend
{
char name[20];
int age;
}friend;
// 演示结构体访问方式的本质
struct mystruct
{
int a;
double b;
char c;
};
// 初步演示结构体对齐访问
struct align
{
char c; // c实际占4字节内存空间
int b; // b实际占4字节内存空间
};
int main(int argc, char **argv)
{
struct people person; // 使用结构体类型定义变量
s1.age = 8; // 定义结构体类型的同时定义变量
printf("s1.age = %d.\n", s1.age); // s1.age = 8.
struct student s2;
s2.age = 10;
printf("s2.age = %d.\n", s2.age); // s2.age = 10.
friend s3; // 使用typedef将类型struct student重命名为student
s3.age = 15;
printf("s3.age = %d.\n", s3.age); // s3.age = 15.
struct mystruct access;
access.a = 10; // 等价于int *p = (int *)&access; *p = 10;
int *p1 = (int *)&access;
printf("*p1 = %d.\n", *p1); // *p1 = 10.
access.b = 4.40; // 等价于double *p = (double *)((int)&access + 4); *p = 4.40;
double *p2 = (double *)((int)&access + 4);
printf("*p2 = %lf.\n", *p2); // *p2 = 4.400000.
access.c = 'f'; // 等价于char *p = (char *)((int)&access + 12); *p = 'f';
char *p3 = (char *)((int)&access + 12);
printf("*p3 = %c.\n", *p3); // *p3 = f.
struct align t; // 初步演示结构体对齐访问
t.c = 'g';
t.b = 40;
printf("sizeof(struct align) = %d.\n", sizeof(struct align));
char *p4 = (char *)&t; // sizeof(struct align) = 8.
printf("*p4 = %c.\n", *p4); // *p4 = g.
int *p5 = (int *)((int)&t + 4);
printf("*p5 = %d.\n", *p5); // *p5 = 40.
return 0;
}
30.struct_align
/*
* 公司:XXXX
* 作者:Rston
* 博客:http://blog.csdn.net/rston
* GitHub:https://github.com/rston
* 项目:C语言结构体对齐访问
* 功能:结构体对齐问题详解。
*/
#include <stdio.h>
//#pragma pack(128)
// 首先结构体起始地址需满足4字节对齐,这个由编译器保证
// 第1个元素:int类型本身的对齐参数是4(在GCC中),a的起始地址为整个结构体的起始地址,自然4字节对齐,
// 但a的结束地址由下一个元素决定。
// 第2个元素:char类型本身的对齐参数是1(在GCC中),则b的起始地址可以随便放,因为a本身占用4字节内存空间,
// a已经满足4字节对齐,则b可直接放(不填充),但b的结束地址由下一个元素决定。
// 第3个元素:short类型本身的对齐参数是2(在GCC中),则c的起始地址必须为偶数地址处,即c不能紧挨着b来存放,
// 解决方案是在b之后添加1字节的填充(padding),然后再开始放c,c的结束地址由整个结构体必须满足4字节对齐决定。
//#pragma pack(1) // 设置编译器对齐方式为1字节对齐
struct mystruct1
{ // 1字节对齐 4字节对齐
int a; // 4 4
char b; // 1 2(1+1)
short c; // 2 2
};
typedef struct mystruct2
{ // 1字节对齐 4字节对齐
char a; // 1 4(1+3)
int b; // 4 4
short c; // 2 4(2+2)
}MyS2;
typedef struct mystruct5
{ // 1字节对齐 4字节对齐
int a; // 4 4
struct mystruct1 s1; // 7 8
double b; // 8 8
int c; // 4 4
}MyS5;
struct stu
{ // 1字节对齐 4字节对齐
char sex; // 1 4(1+3)
int length; // 4 4
char name[10]; // 10 12(10+2)
};
//#pragma() // 设置编译器对齐方式为1字节对齐
// 使用__attribute__((packed))指令设置编译器对齐方式为1字节对齐
struct mystruct11
{ // 1字节对齐 4字节对齐
int a; // 4 4
char b; // 1 2(1+1)
short c; // 2 2
}__attribute__((packed));
struct mystruct22
{ // 1字节对齐 4字节对齐
char a; // 1 4(1+3)
int b; // 4 4
short c; // 2 4(2+2)
}__attribute__((packed));
// 使用__attribute__((aligned(n)))指令设置编译器对齐方式
// 注意只是针对结构体整体变量需要1024整体对齐,而不是结构体中的各个元素都按照1024字节对齐
typedef struct mystruct111
{ // 1字节对齐 4字节对齐 2字节对齐
int a; // 4 4 4
char b; // 1 2(1+1) 2
short c; // 2 2 2
short d; // 2 4(2+2) 2
}__attribute__((aligned(1024))) My111;
int main(int argc, char **argv)
{
// sizeof(struct mystruct1) = 8.
printf("sizeof(struct mystruct1) = %d.\n", sizeof(struct mystruct1));
// sizeof(struct mystruct2) = 12.
printf("sizeof(struct mystruct2) = %d.\n", sizeof(struct mystruct2));
// sizeof(struct mystruct5) = 24.
printf("sizeof(struct mystruct5) = %d.\n", sizeof(struct mystruct5));
// sizeof(struct stu) = 20.
printf("sizeof(struct stu) = %d.\n", sizeof(struct stu));
// sizeof(struct mystruct11) = 7.
printf("sizeof(struct mystruct11) = %d.\n", sizeof(struct mystruct11));
// sizeof(struct mystruct22) = 7.
printf("sizeof(struct mystruct22) = %d.\n", sizeof(struct mystruct22));
// sizeof(My111) = 1024.
printf("sizeof(My111) = %d.\n", sizeof(My111));
return 0;
}