-
结构体是一种数据结构,其特点是可以包含多种类型的成员
-
结构体属于UDT,即用户自定义类型,可以通过关键字struct定义结构体类型,不过使用typedef定义结构体类型更加广泛,使用typedef可以省掉在定义结构体变量时
在定义结构体类型的变量后,便可以通过.或->来实现对结构体成员的访问,实际上也是通过指针访问
struct a temp;
temp.s2 = 1;
(int *)(&temp+sizeof(s1))=1;
//l两者意义相同
//只不过编译器自动识别了每个成员所占内存大小,偏移后访问下一成员
//如果不使用.或->,在使用结构体指针访问结构体成员时,指针每+1,偏移的地址时整个结构体的大小
//所以当我们使用指针方式模拟运算符访问结构体时,需要将指针类型强制转换为成员自身类型
-
结构体的数据对齐
在结构体内,不是每个成员都只占其自身类型大小的,原因是为保证运行效率,编译器32位机情况下默认结构体成员4字节对齐,原因是因为32位机的数据总线为4个字节,因此一次ldr指令加载进来的数据就是4个字节,这样使用效率最高,同时结构体还具有在保证对齐的情况下使用最少内存的特性,因此在两个成员不
在计算结构体占用内存空间大小时,通过下个成员的起始决定前一个是否需要填充,填充多少字节
例如:struct template{ int s1; char s2; short s3; }
s1是int型,刚好占用4个字节,不需要填充,s2占用一个字节,下个成员为short,占用两个字节,所以short类型必须放在两字节处对齐,即偶数字节位,因此在s2后面填充一个字节,最后一个成员的结束地址由结构体的结束地址确定,以保证结构体也是4字节对齐,这里因为已经对齐,所以不需要再进行填充了
同时由于结构体的特性,许多的库函数中用它来封装寄存器,通过指针解引用将结构体类型定位到寄存器起始地址,这样当访问寄存器时,.或->自动帮我们计算好地址偏移量,然后将我们设定好的值填入寄存器 -
#pragma (pack(n)) __ attribute__
- #pragma (pack(n))
#pragma (pack(n))是用来强制编译器对齐类型的
通过#pragma (pack(n))指令确定对齐位数和开始使用这种对齐规则的开始,通过#pragma 指令结束自定义对齐规则 - attribute ((packed))与__attribute__ ((aligned(n)))
__ attribute __ 是GCC定义对齐类型的关键字,与上面不同的是,该关键字只作用于表达式,使用 __attribute __ ((packed))的作用是取消字节对齐,当使用 __attribute __ ((aligned(n))) 修饰结构体类型时,定义的是整个结构体的对齐数,而不是成员的对齐数
这种情况一般根据硬件对齐情况使用,使用其相适配的数据大小可以提高运行效率
- #pragma (pack(n))
struct template __artribute__ ((aligned(16)))temp;
printf("%d",temp); //成员对齐后八字节,结构体按16字节对齐,所以在后面填充8个字节
- ofsetof与container_of宏
offsetof是一个c中自带的宏定义,作用是获取结构体成员的地址偏移量
container_of是一个c中自带的宏定义,作用是通过结构体成员反向获得结构体地址
仅需要的得到三个参数,任意结构体成员的指针,结构体类型,结构体成员名,先是通过指向结构体成员的指针反向获取到结构体成员的变量类型
本质上是通过当前成员的地址减去地址偏移量,得到结构体指针,偏移量是通过offsetof获取到的,通过typeof可以获取到表达式的变量类型类型 - 补充,位域
位域是一种数据结构,通过使用冒号运算符分配需要的位数,目的是为了压缩不必要的空间,这种做法在单片机中的库函数中定义寄存器中的某些特殊功能位使用
位域占用空间的计算,在类型相同的情况下,在当前类型的字节内压缩,在超过类型大小的情况下,增加一个类型大小
在类型不同的情况下,不同编译环境的压缩方式可能不同,例如子啊VC6中就不对不同类型进行压缩,而是分配一个新类型的字节