从gcc的__attribute__((packed))聊到结构体大小的问题
公司的前辈的代码里面 结构体的花括号最后 有__attribute__((packed))字样.以前没见过,所以查了查.学习学习http://blog.sina.com.cn/s/blog_559f6ffc0101dbem.html
__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 used)。
下面的例子中,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------
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 { 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.数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中小的那个值。
从gcc的__attribute__((packed))聊到结构体大小的问题的更多相关文章
- C/C++ sizeof函数解析——解决sizeof求结构体大小的问题
C/C++中不同数据类型所占用的内存大小 32位 64位 char 1 1 int ...
- Windows下struct和union字节对齐设置以及大小的确定(一 简介和结构体大小的确定)
在windows下设置字节对齐大小的方式,目前我了解有三种: 1. 在编译程序时候的编译选项 /Zp[n],如 cl /Zp4 表示对齐大小是4字节: 2. 预处理命令 #pragma pack ...
- 关于C语言中结构体大小计算
结构体大小的计算,.网上说法一大堆还都不一样分什么对齐不对齐,偏移量什么的.. 在此稍微举例简单总结下: 对齐原则:每一成员的结束偏移量需对齐为后一成员类型的倍数 补齐原则:最终大小补齐为成员中最大 ...
- C++字节对齐与结构体大小计算
转载注明出处:http://pppboy.blog.163.com/blog/static/30203796201082494026399/ 感谢原创博主的辛勤成果. 说明: 结构体的sizeof值, ...
- struct结构体大小的计算(内存对齐)
本次实验环境 环境1:Win10, QT 5.12 一. 背景 当普通的类型无法满足我们的需求的时候,就需要用到结构体了.结构体可衍生出结构体数组,结构体还可以嵌套结构体,这下子数据类型就丰富多彩了, ...
- C语言地址对齐(转)--网络编程之结构体大小的计算
什么是地址对齐? 现代计算机中内存空间都是按照字节(byte)划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定变量的时候经常在特定的内存地址访问,这就需要各类型数 ...
- C语言中结构体大小计算
1.普通结构体 struct student { char sex; char a; char b; int age; char name[100]; }; 该结构体大小为108 解答:1.先算str ...
- Linux杂谈: gcc对结构体大小的默认处理方式
1. 发现问题 最近在编写代码过程中发现,对一个结构体进行 sizeof 操作时,有时候大小是填充过的,有时候又没有填充. 那么,如果在代码中没有显示的指定要求编译器进行对齐时,gcc的默认处理是怎样 ...
- sizeof进行结构体大小的判断
typedef struct{ int a; char b;}A_t;typedef struct{ int a; char b; char c;}B_t;typedef ...
随机推荐
- TimeSpan XML序列化
/// <summary> /// 刷新时间 默认为1秒 /// </summary> /// <value>The refresh time.</value ...
- Sublime ctags 函数跳转插件安装
Sublime Text安装插件的方法,主要有以下两种: 1. 直接通过下载安装包安装 在编辑器菜单中点击“Preferences”–“Browse Packages…”打开插件安装目录,然后把下载的 ...
- Java多线程小结
简述 Java是支持多线程编程的语言,线程相比于进程更加轻量级,线程共享相同的内存空间,但是拥有独立的栈.减少了进程建立.销毁的资源消耗.jdk1.5后对java的多线程编程提供了更完善的支持,使得j ...
- OpenGL中的投影使用
OpenGL中的投影使用 在OpenGL中,投影矩阵指定了可视区域的大小和形状.对于正投影与透视投影这两种不同的投影类型,它们分别有各自的用途. 正投影 它适用于2D图形,如文本.建筑画图等.在它的应 ...
- [ES7] Descorator: evaluated & call order
When multiple decorators apply to a single declaration, their evaluation is similar to function comp ...
- JSP中嵌入java代码的标签方式(转)
(1)声明变量或方法 : <%! 声明; %> :慎重使用,因为此方法定义的是全局变量 (2)java片段(scriptlet): <% java代码; %> (3)表达式 ...
- JavaRTS-DTraceProvider
https://docs.oracle.com/javase/realtime/doc_2.0_u1/release/JavaRTSDTraceProvider.html
- css hack 大全
各个浏览器的css hack区别属性: IE6: _zoom:1; IE6/7: *zoom:1; IE6/7/8/9 :\9 各个浏览器的css hack区别规则 IE6: *html{} IE7: ...
- (转)Spring 读书笔记-----使用Spring容器(一)
Spring有两个核心接口:BeanFactory和ApplicationContext,其中ApplicationContext是BeanFactory的子接口.他们都可代表Spring容器,Spr ...
- java的集合框架之一
java是一套很成熟的东西,很多商用的东西都喜欢用它,用的人多,稳定.不过一般也不怎么说起它,因为太常见了,私下里说,写java应用层得就像农民工,每一处都是搭积木,根据设计师的东西如何优雅地搭好积木 ...