用VS写C语言无法使用`__attribute__(packed)`
今天,在Windows上写C语言程序,结果编写的结构体总是无法使用__attribute__((packed)),导致读取的二进制文件字节对不上。搜了半天,得到的解决方法主要是在结构体前加上pragma pack(1);解释为 #pragma pack(1)让编译器将结构体数据强制连续排列。检查CMakeLists.txt和代码均没有发现明显错误。查询网络文章#pragma pack(1) 的意义
gcc中的一个关键字,但是什么阻止__attribute __((packed))进入ISO C / C ++?
hat prevents __attribute__((packed)) from getting into ISO C/C++?
这是我使用C / C ++编写某些类的系统软件的最重要原因之一,但这不过是编译器扩展而已,这很常见。
委员会为什么不考虑正式支持它? 它是否与现有规范中的任何条款不兼容,例如public和private对对象的内存布局有影响吗?
相关讨论
- 对齐,最重要的是。
- 没有" ISO C / C ++"之类的东西。尽管口语化的" C / C ++"非常猖and并且通常可以理解,但是当您专门询问标准化时,我希望关注这些细节。
- @KerrekSB在这里,它似乎是" ISO C和/或ISO C ++"的缩写。这完全是普通的英语用法...我只是使用了"和/或"一词,但您不会抱怨没有逻辑运算符称为"和/或"。
- @KerrekSB"为什么现行的ISO / IEC JTC1 / SC22 / WG21委员会和ISO / IEC JTC1 / SC22 / WG14委员会正在考虑将其纳入其负责的规范中?"你满意吗?
- @nodakai:为什么要这样?如今,打包结构已经没有多大意义了,除非您的系统空间非常有限,甚至那些经常需要对齐的系统。强制打包将增加代码/运行时间,这在这种平台上通常是不那么理想的。最好通过将声明中的成员从大到小排序来手动打包。仅对于编组打包是不够的,因为它不关心编码或尾音。
- @Olaf"如今的打包结构在一般情况下没有多大意义"如果没有它,您的生活还可以,我不劝您使用它。但是对于TCP数据包检查器之类的应用程序,它仍然非常重要。
- @nodakai:是的,但是征求两个基本上无关的机构的联合意见似乎毫无意义。就像问"为什么我的女朋友和你的老板不喜欢寿司?"。
- @Olaf您需要一种在内存中分配数据的通用标准方法,最简单的方法是使用结构对数据进行分区。从大到小对数据进行排序与??本讨论完全无关(请考虑结构中包含uint32_t:2,uint32_t:2的结构)。如果您仍然不明白为什么需要它,请考虑必须完全正确地写入的128位寄存器长度。与使用打包的结构表示这个关系会更容易吗?无论如何,我看不到您的评论与该问题的相关性,如果您看不到为什么使用它,请不要使用它
- @nodakai:不,不是。如我所写,封送/取消封送序列化数据有更好且与实现无关的方法。我有足够多的垃圾代码不必要地依赖于此类黑客。 (我不谈论您必须在每个时钟周期内从CPU压缩掉的系统,无论如何,这些系统都需要其他特定于编译器的特性。)
- @IshayPeled:我想念他们从C标准中删除了struct吗?这是关于将针对特定实现的东西成功地添加到针对众多不同平台的通用标准中。 C比x86或ARM拥有更多的功能!
- ISO委员会不能完成这样的任务,其作用是使利益相关者同样不高兴。每个人都以不同的方式解决了这一共同需求,只有通过添加第十种方式才能实现同等的不快乐。"仁慈的独裁者"可以做到这一点,在C和C ++中没有好的候选人。
- @HansPassant也许Olaf可以胜任工作(;
Why isn't the committee considering to support it officially?
因为没人提议。 与此类建议最接近的是N3986(PDF),仅适用于位域。 讨论中的评论表明委员会将对此感兴趣,但他们希望在尝试推进标准化之前了解现有做法。
从那时起,负责该提案的人决定不再继续执行该提案。
一、概念
在C语言中,结构是一种复合数据类型,其构成元素既可以是基本数据类型(如int、long、float等)的变量,也可以是一些复合数据类型(如数组、结构、联合等)的数据单元。在结构中,编译器为结构的每个成员按其自然边界(alignment)分配空间。各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构的地址相同。
为了使CPU能够对变量进行快速的访问,变量的起始地址应该具有某些特性,即所谓的”对齐”. 比如4字节的int型,其起始地址应该位于4字节的边界上,即起始地址能够被4整除
二、为什么要字节对齐
需要字节对齐的根本原因在于CPU访问数据的效率问题。假设上面整型变量的地址不是自然对齐,比如为0x00000002,则CPU如果取它的值的话需要访问两次内存,第一次取从0x00000002-0x00000003的一个short,第二次取从0x00000004-0x00000005的一个short然后组合得到所要的数据,如果变量在0x00000003地址上的话则要访问三次内存,第一次为char,第二次为short,第三次为char,然后组合得到整型数据。而如果变量在自然对齐位置上,则只要一次就可以取出数据。一些系统对对齐要求非常严格,比如sparc系统,如果取未对齐的数据会发生错误,举个例:
-
char ch[8];
-
char *p = &ch[1];
-
int i = *(int *)p;
运行时会报segment error,而在x86上就不会出现错误,只是效率下降
三、修改C编译器的缺省字节对齐
在缺省情况下,C编译器为每一个变量或是数据单元按其自然对界条件分配空间。一般地,可以通过下面的方法来改变缺省的对界条件:
- 使用伪指令#pragma pack (n),C编译器将按照n个字节对齐。
- 使用伪指令#pragma pack (),取消自定义字节对齐方式。
另外,还有如下的一种方式:
- __attribute((aligned (n))),让所作用的结构成员对齐在n字节自然边界上。如果结构中有成员的长度大于n,则按照最大成员的长度来对齐。
- __attribute__ ((packed)),取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐
举例:
-
#pragma pack(1) //让编译器对这个结构作1字节对齐
-
struct test
-
{
-
char x1;
-
short x2;
-
float x3;
-
char x4;
-
};
-
#pragma pack() //取消1字节对齐,恢复为默认4字节对齐
-
这时候sizeof(struct test)的值为8。
四、编译器对齐原则
1、数据类型自身的对齐值:
对于char型数据,其自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4,单位字节。
2、结构体或者类的自身对齐值:其成员中自身对齐值最大的那个值。
3、指定对齐值:#pragma pack (value)时的指定对齐值value。
4、数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中小的那个值
__attribute__((packed))详解
1. __attribute__ ((packed)) 的作用就是告诉编译器取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐,是GCC特有的语法。这个功能是跟操作系统没关系,跟编译器有关,gcc编译器不是紧凑模式的,我在windows下,用vc的编译器也不是紧凑的,用tc的编译器就是紧凑的。例如:
在TC下:struct my{ char ch; int a;} sizeof(int)=2;sizeof(my)=3;(紧凑模式)
在GCC下:struct my{ char ch; int a;} sizeof(int)=4;sizeof(my)=8;(非紧凑模式)
在GCC下:struct my{ char ch; int a;}__attrubte__ ((packed)) sizeof(int)=4;sizeof(my)=5
2. __attribute__关键字主要是用来在函数或数据声明中设置其属性。给函数赋给属性的主要目的在于让编译器进行优化。函数声明中的__attribute__((noreturn)),就是告诉编译器这个函数不会返回给调用者,以便编译器在优化时去掉不必要的函数返回代码。
GNU C的一大特色就是__attribute__机制。__attribute__可以设置函数属性(Function Attribute)、变量属性(Variable Attribute)和类型属性(Type Attribute)。
__attribute__书写特征是:__attribute__前后都有两个下划线,并且后面会紧跟一对括弧,括弧里面是相应的__attribute__参数。
__attribute__语法格式为:
__attribute__ ((attribute-list))
其位置约束:放于声明的尾部“;”之前。
函数属性(Function Attribute):函数属性可以帮助开发者把一些特性添加到函数声明中,从而可以使编译器在错误检查方面的功能更强大。__attribute__机制也很容易同非GNU应用程序做到兼容之功效。
GNU CC需要使用 –Wall编译器来击活该功能,这是控制警告信息的一个很好的方式。
packed属性:使用该属性可以使得变量或者结构体成员使用最小的对齐方式,即对变量是一字节对齐,对域(field)是位对齐。
如果你看过GPSR协议在TinyOS中的实现,你一定会注意到下面的语句:
typedef struct {
double x;
double y;
} __attribute__((packed)) position_t;
开始我们还可以理解,不久是定义一个结构体嘛!不过看到后面的语句,你可能就会一头雾水了,’ __attribute__((packed))’是什么东西?有什么作用?一连串的疑问马上就会从你脑袋里冒出来。虽然这个对理解整个程序没有什么影响,但我不想让这些疑问一直呆在我的脑子里,负担太重。省得以后念念不忘,而且也许有一天可以用的上呢。搞清楚这个问题吧!
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)));
2)不对齐,结构体的长度,就是各个变量长度的和
struct student
{
char name[7];
uint32_t id;
char subject[5];
} __attribute__ ((packed));
跨平台时基于数据结构的网络通信
网络通信通常分为基于数据结构的和基于流的。HTTP协议就是后者的一个例子。
有时为了提高程序的处理速度和数据处理的方便,会使用基于数据结构的通信(不需要对流进行解析)。但是,当需要在多平台间进行通信时,基于数据结构的通信,往往要十分注意以下几个方面:
[1] 字节序
[2] 变量长度
[3] 内存对齐
在常见的系统架构中(Linux X86,Windows),非单字节长度的变量类型,都是低字节在前,而在某些特定系统中,如Soalris Sparc平台,高字节在前。如果在发送数据前不进行处理,那么由Linux X86发向Soalris Sparc平台的数据值,势必会有极大的偏差,进而程序运行过程中无法出现预计的正常结果,更严重时,会导致段错误。
对于此种情况,我们往往使用同一的字节序。在系统中,有ntohXXX(), htonXXX()等函数,负责将数据在网络字节序和本地字节序之间转换。虽然每种系统的本地字节序不同,但是对于所有系统来说,网络字节序是固定的-----高字节在前。所以,可以以网络字节序为通信的标准,发送前,数据都转换为网络字节序。
转换的过程,也建议使用ntohXXX(), htonXXX()等标准函数,这样代码可以轻松地在各平台间进行移植(像通信这种很少依赖系统API的代码,做成通用版本是不错的选择)。
变量的长度,在不同的系统之间会有差别,如同是Linux2.6.18的平台,在64位系统中,指针的长度为8个字节,而在32位系统中,指针又是4个字节的长度---此处只是举个例子,很少有人会将指针作为数据发送出去。下面是我整理的在64位Linux系统和32位Linux系统中,几种常见C语言变量的长度:
short int long long long ptr time_t
32位 2 4 4 8 4 4
64位 2 4 8 8 8 8
在定义通信用的结构体时,应该考虑使用定常的数据类型,如uint32_t,4字节的固定长度,并且这属于标准C库(C99),在各系统中都可使用。
内存对齐的问题,也与系统是64位还是32位有关。如果你手头有32位和64位系统,不妨写个简单的程序测试一下,你就会看到同一个结构体,即便使用了定常的数据类型,在不同系统中的大小是不同的。对齐往往是以4字节或8字节为准的,只要你写的测试程序,变量所占空间没有对齐到4或8的倍数即可,举个简单的测试用的结构体的例子吧:
struct student
{
char name[7];
uint32_t id;
char subject[5];
};
在每个系统上看下这个结构体的长度吧。
内存对齐,往往是由编译器来做的,如果你使用的是gcc,可以在定义变量时,添加__attribute__,来决定是否使用内存对齐,或是内存对齐到几个字节,以上面的结构体为例:
1)到4字节,同样可指定对齐到8字节。
struct student
{
char name[7];
uint32_t id;
char subject[5];
} __attribute__ ((aligned(4)));
2)不对齐,结构体的长度,就是各个变量长度的和
struct student
{
char name[7];
uint32_t id;
char subject[5];
} __attribute__ ((packed));
今天,在Windows上写C语言程序,结果编写的结构体总是无法使用__attribute__((packed)),导致读取的二进制文件字节对不上。
检查CMakeLists.txt和代码均没有发现明显错误。
···
typedef struct
{
data_packet_header header;
uint16_t* data; // 数据
} attribute((packed)) data_packet_s;
···
想到的第一个解决方法。在代码头加上
extern{
C
}
1
2
3
发现没有用。
搜了半天,得到的解决方法主要是在结构体前加上pragma pack(1);
#pragma pack(1)
typedef struct
{
data_packet_header header;
uint16_t* data; // 数据
} __attribute__((packed)) data_packet_s;
1
2
3
4
5
6
然后竟然就好了。
查询网络文章#pragma pack(1) 的意义是什么。
解释为 #pragma pack(1)让编译器将结构体数据强制连续排列。
visual c++等效于GCC的__attribute__((__packed__))
For some compilers, there is a packing specifier for structs, for example ::
对于一些编译器,有一个用于结构的打包指示符,例如::
RealView ARM compiler has "__packed" Gnu C Compiler has "__attribute__ ((__packed__))" Visual C++ has no equivalent, it only has the "#pragma pack(1)"
I need something that I can put into the struct definition.
我需要一些我可以放入struct定义的东西。
Any info/hack/suggestion ? TIA...
任何信息/攻击/建议吗?蒂雅……
6 个解决方案
#1
23
I don't know a slick way of doing it, but you could possibly do something horrible like this:
我不知道有什么巧妙的方法,但你可能会做一些可怕的事情:
#include "packed.h"
struct Foo { /* members go here */ } PACKED;
#include "endpacked.h"
Then for MSVC, packed.h:
然后MSVC packed.h:
#define PACKED
#pragma pack(push,1)
endpacked.h
endpacked.h
#pragma pack(pop)
#undef PACKED
For gcc, packed.h:
gcc,packed.h:
#define PACKED __attribute__ ((__packed__))
endpacked.h:
endpacked.h:
#undef PACKED
Fundamentally, packing is too platform-dependent. Suppose your packed struct has 8-bit fields in it, and consider some system with a 16-bit byte. It can't have a struct representing your data just by packing - you'd have to know how 8-bit bytes are converted to 16-bit bytes when transferred between the two systems. The struct on the 16bit machine might need bitfields, in which case you'd have to know how the implementation lays them out.
从根本上说,包装过于依赖于平台。假设您的填充结构有8位字段,并考虑一些带有16位字节的系统。它不能仅仅通过打包来表示数据结构——您必须知道在两个系统之间传输的8位字节是如何转换成16位字节的。在16位机器上的结构可能需要位域,在这种情况下,您必须知道实现如何将它们列出来。
So if the code is intended to be generally portable, you may just have to define whatever packed structures you need in a platform-specific section of your header file. Or rather, structure your code so that a future port can do that if it has to.
因此,如果代码想要具有一般的可移植性,那么您可能只需要在头文件的特定于平台的部分中定义您需要的填充结构。或者更确切地说,构造您的代码,以便将来的端口能够做到这一点。
#2
60
You can define PACK like this for GNU gcc
您可以为GNU gcc定义这样的包。
#define PACK( __Declaration__ ) __Declaration__ __attribute__((__packed__))
and like this for Visual C++:
类似于Visual c++:
#define PACK( __Declaration__ ) __pragma( pack(push, 1) ) __Declaration__ __pragma( pack(pop) )
And use it like this:
然后像这样使用:
PACK(
struct myStruct
{
int a;
int b;
});
#3
13
I know this question is old now, but I believe there is a better solution than those posted earlier. It is possible to put the pragma in the MSVC case in the struct declaration line after all. Consider the following:
我知道这个问题现在已经过时了,但我相信有一个更好的解决方案。毕竟,在struct声明行中,可以将pragma放入MSVC案例中。考虑以下:
#ifdef _MSC_VER
# define PACKED_STRUCT(name) \
__pragma(pack(push, 1)) struct name __pragma(pack(pop))
#elif defined(__GNUC__)
# define PACKED_STRUCT(name) struct __attribute__((packed)) name
#endif
Then this can be used like so:
然后可以这样使用:
typedef PACKED_STRUCT() { short a; int b } my_struct_t;
PACKED_SRUCT(my_other_struct) { short a; int b };
etc.
等。
The key here is that the use of the __pragma only needs to be around the declaration line of the struct. This needs to include the struct name if it is given one, hence the name being a parameter to the macro. Of course, this is easy to extend to enum/class, which I'll leave as an exercise to the reader!
这里的关键是,使用__pragma只需要在结构的声明行周围。如果给定一个名称,则需要包含结构名称,因此名称是宏的参数。当然,这很容易扩展到enum/类,我将作为练习留给读者!
The test program on the pack documentation MSDN page is useful to verify this.
软件包文档MSDN页面上的测试程序对验证这一点很有用。
EDIT
编辑
It turns out in my testing I was using the Intel Compiler on Windows. Using icl.exe this approach works without a problem, but with the Microsoft compiler (cl.exe), it does not (tested with 2010 and 2013).
在我的测试中,我使用了Windows上的英特尔编译器。icl的使用。exe这个方法没有问题,但是使用Microsoft编译器(cl.exe),它不会(在2010和2013年测试)。
#4
6
Another solution, depending what compilers you need to support, is to notice that GCC has supported the Microsoft-style packing pragmas since at least version 4.0.4 (the online documentation is available at gnu.org for versions 3.4.6 and 4.0.4 - the pragmas are not described in the former and are in the latter). This lets you just use #pragma pack(push,1)
before a structure definition and #pragma pack(pop)
after the definition and it will compile in either.
另一个解决方案,你需要支持什么编译器,是注意GCC支持微软那样的包装pragmas 4.0.4至少从版本(版本的在线文档可在gnu.org 3.4.6 4.0.4,描述的语法不是前者,后者)。这让您可以在定义后使用#pragma pack(push,1),然后在定义之后使用#pragma pack(pop),它也将在其中编译。
#5
6
You can do it the other way round since GCC supports the VC++ pack related pragmas. Look here for more information.
您可以反过来做,因为GCC支持vc++包相关的pragmas。更多信息请看这里。
Extract...
提取……
For compatibility with Microsoft Windows compilers, GCC supports a set of
#pragma
directives which change the maximum alignment of members of structures (other than zero-width bitfields), unions, and classes subsequently defined. The n value below always is required to be a small power of two and specifies the new alignment in bytes.为了与Microsoft Windows编译器兼容,GCC支持一组#pragma指令,它改变了结构成员的最大对齐方式(除了零宽度的位域)、工会和随后定义的类。下面的n值始终是两个的小幂,并以字节为单位指定新的对齐方式。
#pragma pack(n)
simply sets the new alignment.#pragma pack(n)简单地设置了新的对齐方式。
#pragma pack()
sets the alignment to the one that was in effect when compilation started (see also command line option-fpack-struct[=<n>]
see Code Gen Options).#pragma pack()设置与编译启动时生效的对齐方式(参见命令行选项-fpack-struct[= ],参见代码Gen选项)。
#pragma pack(push[,n])
pushes the current alignment setting on an internal stack and then optionally sets the new alignment.#pragma pack(push[,n])将当前的对齐设置推到一个内部堆栈上,然后可以选择设置新的对齐方式。
#pragma pack(pop)
restores the alignment setting to the one saved at the top of the internal stack (and removes that stack entry).#pragma pack(pop)将对齐设置恢复到保存在内部堆栈顶部的位置(并删除堆栈条目)。
Note that
#pragma pack([n])
does not influence this internal stack; thus it is possible to have#pragma pack(push)
followed by multiple#pragma pack(n)
instances and finalized by a single#pragma pack(pop)
.注意,#pragma pack([n])不影响这个内部堆栈;因此,有可能有#pragma pack(push),然后是多个#pragma pack(n)实例,最后由一个#pragma pack(pop)完成。
Some targets, e.g. i386 and powerpc, support the
ms_struct
#pragma
which lays out a structure as the documented__attribute__((ms_struct))
.一些目标,例如i386和powerpc,支持ms_struct #pragma,它以文档的__attribute__((ms_struct))的形式列出了一个结构。
#pragma ms_struct on
turns on the layout for structures declared.#pragma ms_struct打开了声明结构的布局。
#pragma ms_struct off
turns off the layout for structures declared.#pragma ms_struct关闭了声明结构的布局。
#pragma ms_struct reset
goes back to the default layout.#pragma ms_struct reset返回默认布局。
#6
2
Why do you need something to go in the struct?
为什么你需要一些东西进入struct?
I think #pragma pack(1)
is the same, or am I missing something?
我想#pragma pack(1)是相同的,还是我遗漏了什么?
You can do this:
你可以这样做:
struct Foo
{
#pragma pack(push, 1)
int Bar;
#pragma pack(pop)
};
But it looks ugly.
但是看起来丑陋。
详细链接见:
Visual C++ 相当于 GCC 的 __attribute__ ((__packed__))
Visual C ++相当于GCC的__attribute __((__ package ___))
对于某些编译器,有一个结构的打包说明符,例如::
RealView ARM编译器有“__packed”
Gnu C编译器有“__attribute __((__ package ___))”
Visual C ++没有等价物,它只有“#pragma pack(1)”
我需要一些我可以放入结构定义的东西。
任何信息/黑客/建议?TIA ...
3 回答
TA贡献1465条经验 获得超6个赞
我不知道这样做的光滑方式,但你可能会做一些可怕的事情:
#include "packed.h"struct Foo { /* members go here */ } PACKED;#include "endpacked.h"
那么对于MSVC,packed.h:
#define PACKED#pragma pack(push,1)
endpacked.h
#pragma pack(pop)#undef PACKED
对于gcc,packed.h:
#define PACKED __attribute__ ((__packed__))
endpacked.h:
#undef PACKED
从根本上说,包装过于依赖平台。假设您的压缩结构中包含8位字段,并考虑一些具有16位字节的系统。它只能通过打包来形成一个表示数据的结构 - 你必须知道在两个系统之间传输时如何将8位字节转换为16位字节。16位机器上的结构可能需要位域,在这种情况下,您必须知道实现如何实现它们。
因此,如果代码通常是可移植的,您可能只需要在头文件的特定于平台的部分中定义所需的任何打包结构。或者更确切地说,构造您的代码,以便未来的端口可以做到这一点,如果必须的话。
反对 回复2019-08-27
TA贡献1484条经验 获得超6个赞
您可以为GNU gcc定义这样的PACK
#define PACK( __Declaration__ ) __Declaration__ __attribute__((__packed__))
和Visual C ++一样:
#define PACK( __Declaration__ ) __pragma( pack(push, 1) ) __Declaration__ __pragma( pack(pop) )
并像这样使用它:
PACK(struct myStruct{ int a; int b;});
强调一点:
#pragma pack(4)
typedef struct
{
char buf[3];
word a;
}kk;
#pragma pack()
对齐的原则是min(sizeof(word ),4)=2,因此是2字节对齐,而不是我们认为的4字节对齐。
这里有三点很重要:
1.每个成员分别按自己的方式对齐,并能最小化长度
2.复杂类型(如结构)的默认对齐方式是它最长的成员的对齐方式,这样在成员是复杂类型时,可以最小化长度
3.对齐后的长度必须是成员中最大的对齐参数的整数倍,这样在处理数组时可以保证每一项都边界对齐
补充一下,对于数组,比如:
char a[3];这种,它的对齐方式和分别写3个char是一样的.也就是说它还是按1个字节对齐.
如果写: typedef char Array3[3];
Array3这种类型的对齐方式还是按1个字节对齐,而不是按它的长度.
不论类型是什么,对齐的边界一定是1,2,4,8,16,32,64....中的一个.
声明:
整理自网络达人们的帖子,部分参照MSDN。
作用:
指定结构体、联合以及类成员的packing alignment;
语法:
#pragma pack( [show] | [push | pop] [, identifier], n )
说明:
1,pack提供数据声明级别的控制,对定义不起作用;
2,调用pack时不指定参数,n将被设成默认值;
3,一旦改变数据类型的alignment,直接效果就是占用memory的减少,但是performance会下降;
语法具体分析:
1,show:可选参数;显示当前packing aligment的字节数,以warning message的形式被显示;
2,push:可选参数;将当前指定的packing alignment数值进行压栈操作,这里的栈是the internal compilerstack,同时设置当前的packing alignment为n;如果n没有指定,则将当前的packing alignment数值压栈;
3,pop:可选参数;从internal compilerstack中删除最顶端的record;如果没有指定n,则当前栈顶record即为新的packingalignment数值;如果指定了n,则n将成为新的packing aligment数值;如果指定了identifier,则internalcompilerstack中的record都将被pop直到identifier被找到,然后pop出identitier,同时设置packingalignment数值为当前栈顶的record;如果指定的identifier并不存在于internal compilerstack,则pop操作被忽略;
4,identifier:可选参数;当同push一起使用时,赋予当前被压入栈中的record一个名称;当同pop一起使用时,从internalcompilerstack中pop出所有的record直到identifier被pop出,如果identifier没有被找到,则忽略pop操作;
5,n:可选参数;指定packing的数值,以字节为单位;缺省数值是8,合法的数值分别是1、2、4、8、16。
重要规则:
1,复杂类型中各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个类型的地址相同;
2,每个成员分别对齐,即每个成员按自己的方式对齐,并最小化长度;规则就是每个成员按其类型的对齐参数(通常是这个类型的大小)和指定对齐参数中较小的一个对齐;
3,结构、联合或者类的数据成员,第一个放在偏移为0的地方;以后每个数据成员的对齐,按照#pragmapack指定的数值和这个数据成员自身长度两个中比较小的那个进行;也就是说,当#pragmapack指定的值等于或者超过所有数据成员长度的时候,这个指定值的大小将不产生任何效果;
4,复杂类型(如结构)整体的对齐是按照结构体中长度最大的数据成员和#pragma pack指定值之间较小的那个值进行;这样在成员是复杂类型时,可以最小化长度;
5,结构整体长度的计算必须取所用过的所有对齐参数的整数倍,不够补空字节;也就是取所用过的所有对齐参数中最大的那个值的整数倍,因为对齐参数都是2的n次方;这样在处理数组时可以保证每一项都边界对齐;
6,#pragma pack(n) 与 #pragma pack()必须成对在定义文件中(*.h)出现,不然其它文件引用该文件会出错。
例如:
“filename”: 在包含头后更改了对齐方式,可能是由于缺少 #pragma pack(pop) 封装影响类的布局,并且如果封装在各个头文件中有所更改,则通常会有问题。
退出头文件前使用 #pragma pack(pop) 可解决此警告。
下面的示例生成 C4103:
// C4103.h
#pragma pack(push, 4)
// defintions and declarations
// uncomment the following line to resolve
// #pragma pack(pop)
然后,
// C4103.cpp
// compile with: /LD /W1
#include "c4103.h" // C4103
可以看看我遇到的问题帖子:http://blog.csdn.net/zzhongcy/article/details/42125079
缺省字节对齐方式
更改c编译器的缺省字节对齐方式:
在缺省情况下,c编译器为每一个变量或数据单元按其自然对界条件分配空间;一般地可以通过下面的两种方法来改变缺省的对界条件:
方法一:
使用#pragma pack(n),指定c编译器按照n个字节对齐;
使用#pragma pack(),取消自定义字节对齐方式。
方法二:
__attribute(aligned(n)),让所作用的数据成员对齐在n字节的自然边界上;如果结构中有成员的长度大于n,则按照最大成员的长度来对齐;
__attribute((packed)),取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。
例子
综上所述,下面给出例子并详细分析:
例子一:
#pragma pack(4)
class TestB
{
public:
int aa; //第一个成员,放在[0,3]偏移的位置,
char a; //第二个成员,自身长为1,#pragma pack(4),取小值,也就是1,所以这个成员按一字节对齐,放在偏移[4]的位置。
short b; //第三个成员,自身长2,#pragma pack(4),取2,按2字节对齐,所以放在偏移[6,7]的位置。
char c; //第四个,自身长为1,放在[8]的位置。
};
可见,此类实际占用的内存空间是9个字节。根据规则5,结构整体的对齐是min( sizeof( int ), pack_value ) = 4,所以sizeof( TestB ) = 12;
例子二:
#pragma pack(2)
class TestB
{
public:
int aa; //第一个成员,放在[0,3]偏移的位置,
char a; //第二个成员,自身长为1,#pragma pack(4),取小值,也就是1,所以这个成员按一字节对齐,放在偏移[4]的位置。
short b; //第三个成员,自身长2,#pragma pack(4),取2,按2字节对齐,所以放在偏移[6,7]的位置。
char c; //第四个,自身长为1,放在[8]的位置。
};
可见结果与例子一相同,各个成员的位置没有改变,但是此时结构整体的对齐是min( sizeof( int ), pack_value ) = 2,所以sizeof( TestB ) = 10;
例子三:
#pragma pack(4)
class TestC
{
public:
char a; //第一个成员,放在[0]偏移的位置,
short b; //第二个成员,自身长2,#pragma pack(4),取2,按2字节对齐,所以放在偏移[2,3]的位置。
char c; //第三个,自身长为1,放在[4]的位置。
};
整个类的实际内存消耗是5个字节,整体按照min( sizeof( short ), 4 ) = 2对齐,所以结果是sizeof( TestC ) = 6;
例子四:
struct Test
{
char x1; //第一个成员,放在[0]位置,
short x2; //第二个成员,自身长度为2,按2字节对齐,所以放在偏移[2,3]的位置,
float x3; //第三个成员,自身长度为4,按4字节对齐,所以放在偏移[4,7]的位置,
char x4; //第四个陈冠,自身长度为1,按1字节对齐,所以放在偏移[8]的位置,
};
所以整个结构体的实际内存消耗是9个字节,但考虑到结构整体的对齐是4个字节,所以整个结构占用的空间是12个字节。
例子五:
#pragma pack(8)
struct s1
{
short a; //第一个,放在[0,1]位置,
long b; //第二个,自身长度为4,按min(4, 8) = 4对齐,所以放在[4,7]位置
};
所以结构体的实际内存消耗是8个字节,结构体的对齐是min( sizeof( long ), pack_value ) = 4字节,所以整个结构占用的空间是8个字节。
struct s2
{
char c; //第一个,放在[0]位置,
s1 d; //第二个,根据规则四,对齐是min( 4, pack_value ) = 4字节,所以放在[4,11]位置,
long long e; //第三个,自身长度为8字节,所以按8字节对齐,所以放在[16,23]位置,
};
所以实际内存消耗是24自己,整体对齐方式是8字节,所以整个结构占用的空间是24字节。
#pragma pack()
所以:
sizeof(s2) = 24, s2的c后面是空了3个字节接着是d。
————————————————
版权声明:本文为CSDN博主「zzhongcy」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zzhongcy/article/details/42126351
【内存对齐(二)】__declspec( align(#) )的用法和大小计算
【内存对齐(二)】__declspec( align(#) )的用法和大小计算 - 我的编程乐园 - C++博客
在上面讲到了关于pack的内存对齐和计算方法,这里继续讲实现内存对齐的另一种方式:__declspec( align(#) )
__declspec( align(#) )和#pragma pack( n )有密切联系。
当一个变量或结构体同时受两者影响时,前者的优先级高。
成员的地址决定于前者及后者,其要么是前者的倍数,要么是后者的倍数,要么是成员的大小的倍数,取最小。
结构体最后的大小于前者有关,其要么是前者的倍数,要么是结构体中最大偏移量的倍数,取最大。
要算出最后结果,必须知道两者的值或缺省值。
下面举一个例子来详细的分析:
#include <stdio.h>
#include "stdafx.h"
#include <stdlib.h>
//using namespace std;
#pragma pack( push, 4 )
__declspec( align(32) )struct D
{
int i1;
double d1;
int i2;
int i3;
};
int main()
{
cout << "sizeof(int) = "<<sizeof(int) << endl;
cout << "sizeof(char) = " << sizeof(char) << endl;
cout << "sizeof(double) = " << sizeof(double) << endl;
cout << sizeof(D) << endl;
system("PAUSE");
return 0;
}
这段代码在VS 2010中的运行结果是,sizeof(D)的大小为32,而在Dev C++,C-Free 5.0以及gcc中的结果都似乎20。下面我们来着重讲讲关于__declspec( align(#) )的用法:
正如前面所说的,当有__declspec( align(#) )和pack的时候,__declspec( align(#) )的优先级要高些。所以对于上面这个例子,我们首先来计算出来每一个的大小。
1. 成员的地址如何取?
规则:成员的地址要取pack(n),__declspec( align(m) ),以及成员自身大小这三者之间的最小值,也就是,min(n,m,sizeof(成员变量类型)),那么我们可以对每一个结构体的成员都进行分析。
第一个为int类型,占据4B,所以地址是[0~3].
第二个为double类型,它的地址要根据min(4,32,sizeof(double))来判断,所以应该是4的倍数,也就是相邻着int类型的i1存放。地址是[4~11]。
第三个为int类型,占据4B,同样应该是4的倍数,地址是[12~15].
第四个为int类型,占据4B,地址为[16~19].
从而总的地址是从[0~19]连续存放的20个字节,那么是否sizeof(D)的大小就是20呢?
经过测试,我们可以看到,在VS 2010中,结果是32,why?
这就要用__declspec( align(#) )来解释了。也就是下面第二点的内容。
2. 结构体最后的大小如何决定?
规则:结构体最后的大小与__declspec( align(m) )有关,其要么是它的倍数,要么是结构体中最大偏移量的倍数,取最大。
根据这个规则,这里align是32,而结构体中最大的是double类型,也就是应该是max(32,8)=32,所以最后结构体的大小应该是32的倍数,而明显上面我们看到的实际大小是20B,从而需要扩展到32B。
在这里,就体现了__declspec( align(m) )的强大作用!
同样的,为了体现该语句的作用,我们去掉这个语句,运用我们前面一节内容的知识,来计算并测试sizeof(D),最终不论是在VS 2010还是Dev C++中,运行的结果都是上面我们所预测的20B。
OK,下面回到最后的疑问,也就是前面我们提出的,为何加入了__declspec( align(m) )语句之后,在DevC++和VS 2010的结果不同?
实际上,对于这些内存对齐的处理,不同的编译器可能采取不同的处理,就像前面一节中所说的,我将pack误用为package,导致根本没有达到按照我要求的字节对齐的目的,而且编译器根本不提供任何警告信息。那么,这里合理的解释是:Dev C++不支持这种用法。
通过查阅资料,参照这篇文章【 SSE指令介绍及其C、C++应用 】(SSE指令介绍及其C、C++应用_delphihero的博客-CSDN博客),我们可以看到作者有这么一段话:
“接下来我举一个例子来说明SSE的指令函数是如何使用的,必须要说明的是我以下的代码都是在VC7.1的平台上写的,不保证对其它如Dev-C++、Borland C++等开发平台的完全兼容。”
“这里要注意一下,我使用了__declspec(align(16))做为数组定义的修释符,这表示该数组是以16字节为边界对齐的,因为SSE指令只能支持这种格式的内存数据。
我们在这里看到了SSE算的强大,相信它会成为多媒体程序员手中用来对付无穷尽流媒体数据的一把利剑。我后面还会写一些关于SSE算法更复杂应用的文章,敬请关注,感谢您抽时间阅读!
”
从这篇文章我们可以看到,SSE指令集的情况下,在VC 7.1下才支持__declspec(align(16))这种用法,而对于其他平台不一定有效。而前面我们使用的Dev C++以及C-Free,都是基于g++或者MinGW,不一定会支持这种方式,或者说,不一定按照这种内存对齐的建议来做,也就造成了结果的不同。
下面我们来继续探讨结构体中有结构体的情况。
先看看下面这段代码:
#include <stdio.h>
#include "stdafx.h"
#include <stdlib.h>
//using namespace std;
#pragma pack( push, 4 )
__declspec( align(32) )struct D
{
int i1;
double d1;
int i2;
int i3;
};
__declspec( align(16) ) struct E
{
int i1;
D m_d;
int i2;
};
int main()
{
cout << "sizeof(int) = "<<sizeof(int) << endl;
cout << "sizeof(char) = " << sizeof(char) << endl;
cout << "sizeof(double) = " << sizeof(double) << endl;
cout << sizeof(D) << endl;
cout << sizeof(E) << endl;
system("PAUSE");
return 0;
}
最后运行的结果是sizeof(E)为96,为何会是这个结果呢?我们来详细讲解下。
对于结构体E,第一个元素为int类型,所以占据[0~3]地址单元。
第二个元素是一个结构体,该结构体由于受上面__declspec( align(32) )的影响,优先级高,所以起始地址是32的倍数,而且大小为32B,从而应该放置在[32~63]单元处。
最后一个是int类型的变量,大小为4,所以应该是4的倍数,地址为[64~67]。
故结构体E的大小应该是从[0~67],占据68B,而由于前面还有限制__declspec( align(16) ),同时成员变量的最大偏移是sizeof(D)=32,所以我们最后这个结构体的大小应该是他们中最大值的倍数,也就是32的倍数,68向上取32的倍数应该是96.故结果为96.
最后仍然是上面平台的问题,在Dev C++和G++下面的结果不同,原因上面解释了。
MSDN:
“The sizeof value for any structure is the offset of the final member, plus that member's size, rounded up to the nearest multiple of the largest member alignment value or the whole structure alignment value, whichever is greater.”
中文:
“sizeof的结果都是结构体中最后的一个成员变量加上它的大小,再加上一个填充容量(padding),这个填充大小是成员变量最大的一个对齐参数或整个结构体的对齐参数的倍数,取哪个决定于哪个对齐参数较大”
ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.en/dv_vclang/html/e4209cbb-5437-4b53-b3fe-ac264501d404.htm
ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.en/dv_vclang/html/9cb63f58-658b-4425-ac47-af8eabfc5878.htm
P.S.:上面是关于内存对齐的研究,如有谬误,欢迎指出!
附参考资料和拓展:
1. #pragma pack :http://blog.sina.com.cn/s/blog_492aa57901008y3h.html
2. #pragma pack( n )和__declspec( align(#) ) 的偏移量计算方法: #pragma pack( n )和__declspec( align(#) ) 的偏移量计算方法_whoismickey的博客-CSDN博客
3. #pragma pack(push,1) (pop) :#pragma pack(push,1) (pop)_鱼翅1013的博客-CSDN博客
4. 关于pragma pack的用法(四) C++中的内存对齐问题: 关于pragma pack的用法(四) C++中的内存对齐问题 - - C++博客
5. SSE指令介绍及其C、C++应用:SSE指令介绍及其C、C++应用_delphihero的博客-CSDN博客
6. c++中__declspec用法总结: http://sealbird.javaeye.com/blog/855096
更多推荐
所有评论(0)