从gcc的__attribute__((packed))聊到结构体⼤⼩的问题斜面的作用
__attrubte__ ((packed)) 的作⽤就是告诉编译器取消结构在编译过程中的优化对齐,按照实际占⽤字节数进⾏对齐。是GCC特有的语法。这个功能是跟操作系统没关系,跟编译器有关,gcc编译器不是紧凑模式的,我在windows下,⽤vc的编译器也不是紧凑的
-----⾮紧凑模式:编译器有字节对齐的⾃动优化
#define unsigned char __u8; //优秀的代码设计在结构体中⽤到了int,short,char,long,等都应该定义这⼏⾏
#define unsigned short __u16;
定义的四种结构体
struct str_struct{
__u8 a;
__u8 b;
__u8 c;
__u16 d;
} __attribute__ ((packed));typedef struct {
__u8 a;
__u8 b;
__u8 c;
__u16 d;
} __attribute__ ((packed)) str;
typedef struct {
__u8 a;
__u8 b;
__u8 c;
__u16 d;
}str_temp __attribute__ ((packed));
typedef struct {
__u8 a;
__u8 b;
__u8 c;
__u16 d;
}str_nopacked;
int main(void)
{
cout<< sizeof(struct str_struct)<<endl;
cout<< sizeof(str)<<endl;
cout<< sizeof(str_temp)<<endl;
cout<< sizeof(str_nopacked)<<endl;
return 0;
}
编译运⾏:
sizeof str_struct = 5 (__u8是⼀个字节 __u16是两个字节)
sizeof str = 5
sizeof str_temp = 6 (5个字节是不对齐的,所以扩展⼀个字节给对齐)
sizeof str_nopacked = 6
讲解:
packed属性:使⽤该属性可以使得变量或者结构体成员使⽤最⼩的对齐⽅式,即对变量是⼀字节对齐,对域(field)是位对齐。
GNU C的⼀⼤特⾊(却不被初学者所知)就是__attribute__机制。__attribute__可以设置函数属性(Function Attribute)、变量属性 (Variable Attribute)和类型属性(Type Attribute)。
__attribute__语法格式为:__attribute__ ((attribute-list)) 其位置约束为:放于声明的尾部“;”之前。
packed是类型属性(Type Attribute)的⼀个参数,使⽤packed可以减⼩对象占⽤的空间。(需要注意的是,attribute属性的效⼒与你的连接器也有关,如果你的连接器最⼤只⽀持16字节对齐,那么你此时定义32字节对齐也是⽆济于事的。)
使⽤该属性对struct或者union类型进⾏定义,设定其类型的每⼀个变量的内存约束。当⽤在enum类型定义时,暗⽰了应该使⽤最⼩完整的类型(it indicates that the smallest integral type should be ud)。
下⾯的例⼦中,my-packed-struct类型的变量数组中的值会紧凑在⼀起,但内部的成员变量s不会被“pack”,如果希望内部的成员变量也被packed的话,my-unpacked-struct也需要使⽤packed进⾏相
应的约束。
struct my_unpacked_struct {
char c;
int i;
};struct my_packed_struct
{
char c;
int i;
struct my_unpacked_struct s; }__attribute__ ((__packed__));
内存对齐,往往是由编译器来做的,如果你使⽤的是gcc,可以在定义变量时,添加__attribute__,来决定是否使⽤内存对齐,或是内存对齐到⼏个字节,以上⾯的结构体为例:
1)到4字节,同样可指定对齐到8字节。
struct student
{
char name[7];
uint32_t id;
char subject[5];
} __attribute__ ((aligned(4)));
---------8+4+8==20------
delta函数2)不对齐,结构体的长度,就是各个变量长度的和
struct student
{
char name[7];
uint32_t id;
char subject[5];
} __attribute__ ((packed));
-------7+4+5==16---------
--------------------------------------------------------------------
#pragma pack(n) 对齐⽤法详解
什么是对齐,以及为什么要对齐:
现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定变量的时候经常在特定的内存地址访问,这就需要各类型数据按照⼀定的规则在空间上排列,⽽不是顺序的⼀个接⼀个的排放,这就是对齐。 对齐的作⽤和原因:各个硬件平台对存储空间的处理上有很⼤的不同。⼀些平台对某些特定类型的数据只能从某些特定地址开始存取。其他平台可能没有这种情况, 但是最常见的是如果不按照适合其平台要求对数据存放进⾏对齐,会在存取效率上带来损失。⽐如有些平台每次读都是从偶地址开始,如果⼀个int型(假设为 32位
系统)如果存放在偶地址开始的地⽅,那么⼀个读周期就可以读出,⽽如果存放在奇地址开始的地⽅,就可能会需要2个读周期,并对两次读出的结果的⾼低 字节进⾏拼凑才能得到该int数据。显然在读取效率上下降很多。这也是空间和时间的博弈。
对齐的实现 :
通常,我们写程序的时候,不需要考虑对齐问题。编译器会替我们选择时候⽬标平台的对齐策略。当然,我们也可以通知给编译器传递预编译指令⽽改变对指定数据的对齐⽅法。
但是,正因为我们⼀般不需要关⼼这个问题,所以因为编辑器对数据存放做了对齐,⽽我们不了解的话,常常会对⼀些问题感到迷惑。最常见的就是struct数据结构的sizeof结果,出乎意料。为此,我们需要对对齐算法所了解。
作⽤:
指定结构体、联合以及类成员的packing alignment;
语法:
#pragma pack( [show] | [push | pop] [, identifier], n )
语法具体分析:
1,show:可选参数;显⽰当前packing aligment的字节数,以warning message的形式被显⽰;
2,push:可选参数;将当前指定的packing alignment数值进⾏压栈操作,这⾥的栈是the internal compiler stack,同时设置当前的packing alignment为n;如果n 没有指定,则将当前的packing alignment数值压栈;
3,pop:可选参数;从internal compiler stack中删除最顶端的record;如果没有指定n,则当前栈顶record即为新的packing alignment数值;如果指定了n,则n将成为新的packing aligment数值;如果指定了identifier,则internal compiler stack中的record都将被pop直到identifier被找到,然后pop出identitier,同时设置packing alignment数值为当前栈顶的record;如果指定的identifier并不存在于internal compiler stack,则pop操作被忽略;
4,identifier:可选参数;当同push⼀起使⽤时,赋予当前被压⼊栈中的record⼀个名称;当同pop⼀起使⽤时,从internal compiler stack中pop出所有的record直到identifier被pop出,如果identifier没有被找到,则忽略pop操作;
5,n:可选参数;指定packing的数值,以字节为单位;缺省数值是8,合法的数值分别是1、2、4、8、16。
挥汗如雨重要规则:
1,复杂类型中各个成员按照它们被声明的顺序在内存中顺序存储,第⼀个成员的地址和整个类型的地址相同;
情郁于中的意思2,每个成员分别对齐,即每个成员按⾃⼰的⽅式对齐,并最⼩化长度;规则就是每个成员按其类型的对齐参数(通常是这个类型的⼤⼩)和指定对齐参数中较⼩的⼀个对齐;
3,结构、联合或者类的数据成员,第⼀个放在偏移为0的地⽅;以后每个数据成员的对齐,按照#pragma pack指定的数值和这个数据成员⾃⾝长度两个中⽐较⼩的那个进⾏;也就是说,当#pragma pack指定的值等于或者超过所有数据成员长度的时候,这个指定值的⼤⼩将不产⽣任何效果;
4,复杂类型(如结构)整体的对齐<;注意是“整体”>是按照结构体中长度最⼤的数据成员和#pragma pack指定值之间较⼩的那个值进⾏;这样在成员是复杂类型时,可以最⼩化长度;
5,结构整体长度的计算必须取所⽤过的所有对齐参数的整数倍,不够补空字节;也就是取所⽤过的所有对齐参数中最⼤的那个值的整数倍,因为对齐参数都是2的n次⽅;这样在处理数组时可以保证每⼀项都边界对齐;
在相同的对齐⽅式下,结构体内部数据定义的顺序不同,结构体整体占据内存空间也不同,如下:
设结构体如下定义:
struct A
3322动态域名
{
int a;
char b;
short c;
}; 结构体A中包含了
4字节长度的int⼀个,
1字节长度的char⼀个
2字节长度的short型数据⼀个。
所以A⽤到的空间应该是7字节。
十五月儿圆
但是因为编译器要对数据成员在空间上进⾏对齐。所以使⽤sizeof(strcut A)值为8。
struct B
{
char b;
int a;
short c;
};
这时候同样是总共7个字节的变量,但是sizeof(struct B)的值却是12
下⾯我们使⽤预编译指令#progma pack (value)来告诉编译器,使⽤我们指定的对齐值来取代缺省的。
#progma pack (2)
struct C
{
char b;
int a;
short c;
};
#progma pack ()
sizeof(struct C)值是8。
#progma pack (1)
struct D
{
char b;
int a;
潘金莲武松short c;
};
#progma pack ()
sizeof(struct D)值为7。
对于char型数据,其⾃⾝对齐值为1,对于short型为2,对于int,float,double类型,其⾃⾝对齐值为4,单位字节。
这⾥⾯有四个概念值:
1.数据类型⾃⾝的对齐值:就是上⾯交代的基本数据类型的⾃⾝对齐值。 (对于char型数据,其⾃⾝对齐值为1)
2.指定对齐值:#progma pack (value)时的指定对齐值value。
3.结构体或者类的⾃⾝对齐值:其数据成员中⾃⾝对齐值最⼤的那个值。
4.数据成员、结构体和类的有效对齐值:⾃⾝对齐值和指定对齐值中⼩的那个值。
飞翔的鸟