我想知道是否有人可以向我解释#pragma pack预处理器语句是做什么的,更重要的是,为什么要使用它。
#pragma pack
我检查了MSDN页面,它提供了一些见解,但我希望从有经验的人那里听到更多。我以前在代码中见过它,尽管我似乎再也找不到在哪里了。
你可能只在对某些硬件(例如内存映射设备)编码时才会使用它,这些硬件对寄存器排序和对齐有严格的要求。
然而,这似乎是实现这一目标的一个相当生硬的工具。一个更好的方法是在汇编程序中编写一个迷你驱动程序,并给它一个C调用接口,而不是摸索这个pragma。
它告诉编译器要将结构中的对象对齐到的边界。例如,如果我有这样的东西:
struct foo { char a; int b; };
对于典型的32位机器,您通常“希望”在a和b之间有3个字节的填充,以便b将在4字节边界着陆,以最大化其访问速度(这是通常默认情况下发生的情况)。
a
b
然而,如果你必须匹配一个外部定义的结构,你想要确保编译器完全根据外部定义来布局你的结构。在这种情况下,你可以给编译器一个#pragma pack(1)来告诉它不在成员之间插入任何填充——如果结构的定义包括成员之间的填充,你显式地插入它(例如,通常是命名为unusedN或ignoreN的成员,或类似的顺序)。
#pragma pack(1)
unusedN
ignoreN
#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),上面的结构体将像这样布局:
sizeof(Test)
#pragma
| 1 | | AA(1) | | BB(1) | | BB(2) | | BB(3) | | BB(4) | | CC(1) |
而sizeof(Test)将是1 &乘以;6 = 6。
使用#pragma pack(2),上面的结构体将像这样布局:
#pragma pack(2)
| 1 | 2 | | AA(1) | pad.. | | BB(1) | BB(2) | | BB(3) | BB(4) | | CC(1) | pad.. |
而sizeof(Test)将是2 &乘以;4 = 8。
struct中变量的顺序也很重要。变量顺序如下:
struct Test { char AA; char CC; int BB; };
而使用#pragma pack(2),结构体将像这样布局:
| 1 | 2 | | AA(1) | CC(1) | | BB(1) | BB(2) | | BB(3) | BB(4) |
和sizeOf(Test)将是3 × 2 = 6。
sizeOf(Test)
数据元素(例如类和结构的成员)通常在当前一代处理器的WORD或DWORD边界上对齐,以提高访问时间。在32位处理器上,在不能被4整除的地址处检索DWORD至少需要一个额外的CPU周期。因此,如果你有三个char成员char a, b, c;,它们实际上倾向于占用6或12字节的存储空间。
char a, b, c;
#pragma允许你覆盖这个,以实现更有效的空间使用,以访问速度为代价,或为了不同编译器目标之间存储的数据的一致性。从16位代码到32位代码的转换给我带来了很多乐趣;我预计移植到64位代码也会对某些代码造成同样的困扰。
编译器五月将结构成员放在特定的字节边界上,以提高特定体系结构上的性能。这可能会在成员之间留下未使用的填充。结构填料迫使构件连续。
这可能很重要,例如,如果您需要一个结构符合特定的文件或通信格式,其中您需要数据位于序列中的特定位置。然而,这种用法不处理端部性问题,因此尽管使用了,但它可能是不可移植的。
它还可以精确地覆盖一些I/O设备(例如UART或USB控制器)的内部寄存器结构,以便通过结构而不是直接地址访问寄存器。
编译器可以对结构中的成员进行对齐,以在特定平台上实现最大性能。#pragma pack指令允许你控制对齐。通常,您应该将其保留为默认值以获得最佳性能。如果你需要将一个结构传递给远程机器,你通常会使用#pragma pack 1来排除任何不想要的对齐。
#pragma pack 1
#pragma用于向编译器发送不可移植(仅在此编译器中)消息。像禁用某些警告和包装结构是常见的原因。如果在编译时将警告作为错误标志打开,则禁用特定警告特别有用。
#pragma pack特别用于指示被打包的结构体不应该让其成员对齐。当您有一个内存映射接口到某个硬件,并且需要能够准确控制不同结构成员指向的位置时,它非常有用。这显然不是一个很好的速度优化,因为大多数机器在处理对齐数据时要快得多。
然后用#pragma pack(push,1)和#pragma pack(pop)来撤销
#pragma pack(push,1)
#pragma pack(pop)
我以前在代码中使用过它,不过只是用于与遗留代码进行接口。这是一个Mac OS X Cocoa应用程序,需要从早期的Carbon版本加载首选项文件(它本身向后兼容最初的M68k System 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
注意结构a的大小与字节数完全相同,但结构b添加了填充(有关填充的详细信息,请参阅这)。通过这样做,而不是#pragma包,你可以控制将“连线格式”转换为适当的类型。例如,“char 2[2]”转换为“short int”等等。
为什么要使用它?
减少结构的内存
为什么不应该使用它?