#pragma包效果
我想知道是否有人可以向我解释#pragma pack
预处理器语句做了什么,更重要的是,为什么要使用它。
我检查了MSDN页面 ,这提供了一些见解,但我希望听到有经验的人更多。 我之前在代码中看过,但似乎无法找到。
#pragma pack
指示编译器使用特定的对齐方式打包结构成员。 大多数编译器,当你声明一个结构时,会在成员之间插入填充,以确保它们与内存中的适当地址(通常是该类型的大小的倍数)对齐。 这样可以避免某些与访问未正确对齐的变量相关的体系结构的性能损失(或者完全错误)。 例如,给定4个字节的整数和下面的结构:
struct Test { char AA; int BB; char CC; };
编译器可以选择将结构放在内存中,如下所示:
| 1 | 2 | 3 | 4 | | AA(1) | pad.................. | | BB(1) | BB(2) | BB(3) | BB(4) | | CC(1) | pad.................. |
和sizeof(Test)
将是4×3 = 12,即使它只包含6个字节的数据。 #pragma
(就我所知)最常见的用例是在使用硬件设备时,您需要确保编译器不会将填充插入到数据中,并且每个成员都遵循前一个。 使用#pragma pack(1)
,上面的结构将按照如下布局:
| 1 | | AA(1) | | BB(1) | | BB(2) | | BB(3) | | BB(4) | | CC(1) |
sizeof(Test)
将是1×6 = 6。
使用#pragma pack(2)
,上面的结构可以像这样布局:
| 1 | 2 | | AA(1) | pad.. | | BB(1) | BB(2) | | BB(3) | BB(4) | | CC(1) | pad.. |
sizeof(Test)
将是2×4 = 8。
#pragma
用于向编译器发送不可移植的消息(如在此编译器中)。 像禁用某些警告和打包结构是常见的原因。 如果您将警告标记为错误标志,则禁用特定的警告特别有用。
#pragma pack
专门用于表示被打包的结构不应该有其成员对齐。 当你有一个硬件的内存映射接口,并且需要能够精确地控制不同的结构成员所指向的位置时,这很有用。 这显然不是一个很好的速度优化,因为大多数机器处理对齐的数据要快得多。
它告诉编译器将结构中的对象与边界对齐的边界。 例如,如果我有这样的东西:
struct foo { char a; int b; };
对于一个典型的32位机器,通常需要在a
和b
之间填充3个字节,以便b
能够以4个字节的边界着陆,以最大限度地提高访问速度(这是默认情况下通常会发生的情况)。
但是,如果您必须匹配外部定义的结构,则要确保编译器根据外部定义精确地布局结构。 在这种情况下,您可以给编译器一个#pragma pack(1)
来告诉它不要在成员之间插入任何填充 – 如果结构的定义包含成员之间的填充,则显式插入它(例如, unusedN
或ignoreN
,或者按顺序)。
为了改善访问时间,数据元素(例如类和结构的成员)通常在当前代处理器的WORD或DWORD边界上对齐。 在不能被4整除的地址处检索DWORD需要在32位处理器上至少有一个额外的CPU周期。 所以,如果你有三个char成员char a, b, c;
,他们实际上往往需要6或12个字节的存储空间。
#pragma
允许你重写这个来实现更有效的空间使用,代价是访问速度,或者是不同编译器目标之间存储数据的一致性。 从16位到32位的代码转换我有很多乐趣, 我期望移植到64位代码将导致一些代码相同的头痛。
编译器可以将结构中的成员对齐,以在特定平台上实现最高性能。 #pragma pack
指令允许你控制对齐。 通常情况下,您应该保持默认状态以达到最佳性能 如果您需要将结构传递给远程机器,通常会使用#pragma pack 1
来排除任何不需要的对齐。
如果您对某些硬件(例如存储器映射设备)进行编码,那么对于寄存器排序和对齐有严格的要求,您可能只想使用它。
然而,这看起来像一个相当钝的工具来实现这一目标。 更好的方法是在汇编器中编写一个微型驱动程序,并给它一个C调用接口,而不是摸索这个编译指示。
出于在特定体系结构上的性能原因,编译器可以将结构成员放置在特定的字节边界上。 这可能会在成员之间留下未使用的填充。 结构包装迫使会员连续。
例如,如果您需要一个结构来符合某个特定文件或通信格式,而这些数据需要数据位于序列中的特定位置,则这可能很重要。 然而,这种用法并不涉及端到端问题,因此虽然使用,但可能不是可移植的。
它也可以精确地覆盖某些I / O设备的内部寄存器结构,例如UART或USB控制器,以便通过结构访问寄存器而不是直接访问地址。
我之前在代码中使用过它,只是为了与传统代码进行交互。 这是一个Mac OS X Cocoa应用程序,需要加载早期的Carbon版本的偏好文件(它本身与原始的M68k系统6.5版本向后兼容…你可以这样做)。 原始版本中的首选项文件是配置结构的二进制转储文件,它使用#pragma pack(1)
来避免占用额外的空间和保存垃圾(即否则会在结构中的填充字节)。
代码的原始作者还使用#pragma pack(1)
来存储在进程间通信中用作消息的结构。 我认为这里的原因是为了避免未知的或改变的填充大小的可能性,因为代码有时通过从开始(ewww)计算大量字节来查看消息结构的特定部分。
我已经看到人们使用它来确保一个结构占用整个缓存行,以防止在多线程上下文中的错误共享。 如果你打算有大量的默认松散打包的对象,它可以节省内存并提高缓存的性能来打包它们,尽管未对齐的内存访问通常会减慢速度,所以可能会有不利的一面。
请注意,还有其他一些方法可以实现#pragma pack提供的数据一致性(例如,有些人使用#pragma pack(1)作为应该通过网络发送的结构)。 例如,请参阅以下代码及其后续输出:
#include <stdio.h> struct a { char one; char two[2]; char eight[8]; char four[4]; }; struct b { char one; short two; long int eight; int four; }; int main(int argc, char** argv) { struct a twoa[2] = {}; struct b twob[2] = {}; printf("sizeof(struct a): %i, sizeof(struct b): %i\n", sizeof(struct a), sizeof(struct b)); printf("sizeof(twoa): %i, sizeof(twob): %i\n", sizeof(twoa), sizeof(twob)); }
输出如下:sizeof(struct a):15,sizeof(struct b):24 sizeof(twoa):30,sizeof(twob):48
注意struct a的大小正好是字节数,但是struct b已经添加了padding(有关padding的详细信息,请参阅此处)。 通过这样做而不是#pragma包,您可以控制将“线格式”转换为适当的类型。 例如,“char two [2]”变成“short int”等等。
- #pragma pack(n)只需设置新的对齐方式。
- #pragma pack()将对齐设置为编译开始时有效的对齐方式。
- #pragma pack(push [,n])将内部堆栈上的当前对齐设置压入,然后可以选择设置新对齐。
- #pragma pack(pop)将对齐设置恢复为保存在内部堆栈顶部的对齐设置(并删除该堆栈条目)。 注意#pragma pack([n])不会影响这个内部堆栈。 因此可以有#pragma pack(push)后跟多个#pragma pack(n)实例,并由一个#pragma pack(pop)来完成。
例子:
#pragma pack(push, 1) // exact fit - no padding struct MyStruct { char b; int a; int array[2]; }; #pragma pack(pop) //back to whatever the previous packing mode was Or #pragma pack(1) // exact fit - no padding struct MyStruct { char b; int a; int array[2]; }; #pragma pack() //back to whatever the previous packing mode was Or #pragma pack(1) // exact fit - no padding struct MyStruct { char b; int a; int array[2]; };