我想知道是否有人能向我解释#pragma pack预处理器语句的作用,更重要的是,为什么要使用它。
我查看了提供一些见解的msdn页面,但我希望从有经验的人那里听到更多信息。我以前在代码中见过它,不过我好像找不到了。
- It forces a particular alignment/packing of a structure,but like all #pragmathey are implementation defined.
- 地址和数据类型的大小在哪里;如果数据不正确,则检查。
#pragma pack指示编译器用特定的对齐方式打包结构成员。大多数编译器在声明结构时,都会在成员之间插入填充,以确保它们与内存中适当的地址对齐(通常是类型大小的倍数)。这避免了与访问未正确对齐的变量相关联的某些架构上的性能损失(或直接错误)。例如,给定4字节整数和以下结构:
1 2 3 4 5 6
| struct Test
{
char AA;
int BB;
char CC;
}; |
编译器可以选择在内存中这样布局结构:
1 2 3 4 5
| | 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 2 3 4 5 6 7 8
| | 1 |
| AA(1) |
| BB(1) |
| BB(2) |
| BB(3) |
| BB(4) |
| CC(1) | |
而sizeof(Test)为1倍;6=6。
使用#pragma pack(2)时,上述结构的布局如下:
1 2 3 4 5 6
| | 1 | 2 |
| AA(1) | pad.. |
| BB(1) | BB(2) |
| BB(3) | BB(4) |
| CC(1) | pad.. | |
而sizeof(Test)为2倍;4=8。
- 这可能值得增加包装的缺点。(最佳情况下,未对齐的对象访问速度较慢,但在某些平台上会导致错误。)
- 似乎上面提到的一致性"性能惩罚"实际上对某些系统是有益的,danluu.com/3c-conflict。
- 你会得到什么?
- @加夫,英格的链接是否意味着包装实际上是有益的(与你上面写的相反)?
- @起搏器不是真的。这篇文章讨论了一些相当极端的对齐(在4KB边界上对齐)。CPU期望各种数据类型有一定的最小对齐,但在最坏的情况下,这些类型需要8字节对齐(不计算可能需要16或32字节对齐的向量类型)。不在这些边界上对齐通常会给您带来显著的性能损失(因为一个负载可能需要两个操作而不是一个操作),但类型要么对齐得很好,要么不对齐。比这更严格的对齐不会给您带来任何好处(并且会破坏缓存利用率)
- 换句话说,double应该在8字节边界上。把它放在7字节的边界上会损害性能。但是,如果将它放在16、32、64或4096字节的边界上,就不会比8字节的边界更高。您将从CPU获得相同的性能,而缓存利用率将因本文中概述的原因而变得更差。
- 因此,教训不是"包装是有益的"(包装违反了类型的自然对齐方式,从而损害了性能),而是简单地说,"不要超出所需的范围过度对齐"。
- @英格亨利克森,你同意贾夫的评价吗?
- 关于@jalf所说的错误,这里您可以看到我遇到的一个问题很可能是由于pragma pack s(我还没有100%确定,因为正如所解释的,它完全是随机的,但pragma pack听起来像是罪魁祸首)stackoverflow.com/questions/30020759/…
- @jalf您提到pragma包可能会在某些平台上导致错误。你能就这个问题详细阐述一下吗?
#pragma用于向编译器发送非可移植(仅在本编译器中)消息。禁用某些警告和打包结构是常见原因。如果编译时启用了警告作为错误标志,则禁用特定警告尤其有用。
#pragma pack专门用于表示所包装的结构不应使其成员对齐。当您有一个内存映射接口到一个硬件上,并且需要能够精确地控制不同结构成员指向的位置时,这是非常有用的。这显然不是一个很好的速度优化,因为大多数机器处理对齐数据的速度要快得多。
- 要在以后撤消,请执行以下操作:pragma pack(push,1)和pragma pack(pop)
它告诉编译器将结构中的对象与之对齐的边界。例如,如果我有这样的东西:
1 2 3 4
| struct foo {
char a;
int b;
}; |
对于典型的32位机器,您通常"希望"在a和b之间有3个字节的填充,这样b将以4个字节的边界着陆,以最大化其访问速度(这是默认情况下通常会发生的情况)。
但是,如果必须匹配外部定义的结构,则要确保编译器根据外部定义准确地布局结构。在这种情况下,您可以给编译器一个#pragma pack(1),告诉它不要在成员之间插入任何填充--如果结构的定义包括成员之间的填充,则显式地插入它(例如,通常使用名为unusedN或ignoreN的成员,或按该顺序插入的某些成员)。
- "您通常希望"在A和B之间有3个字节的填充,这样B将以4个字节的边界着陆,以最大化其访问速度"-3个字节的填充将如何最大化访问速度?
- @ashwin:把b放在4字节的边界上意味着处理器可以通过发出一个4字节的负载来加载它。尽管这在一定程度上取决于处理器,但如果它处于一个奇怪的边界,那么加载它很可能需要处理器发出两个单独的加载指令,然后使用移位器将这些部分组合在一起。典型的惩罚是该项目的3倍慢负载。
- …如果您查看用于读取对齐和未对齐int的汇编代码,则对齐读取通常是一个助记键。未对齐的读取可以是10行程序集,因为它可以将int分为多个字节,并将其放在寄存器的正确位置。
- @SF.:它可能是——但即使不是,也不要被误导——在一个x86 CPU上(举一个明显的例子),操作是在硬件上执行的,但是您仍然会得到大致相同的操作集,并且速度会减慢。
数据元素(例如类和结构的成员)通常与当前生成处理器的字或双字边界对齐,以缩短访问时间。在不能被4整除的地址上检索一个双字需要32位处理器上至少一个额外的CPU周期。因此,如果您有三个char成员char a, b, c;,它们实际上会占用6或12字节的存储空间。
#pragma允许您重写它,以牺牲访问速度或不同编译器目标之间存储数据的一致性,从而实现更高效的空间使用。从16位代码到32位代码的转换让我很开心;我希望移植到64位代码会给某些代码带来同样的麻烦。
- 实际上,char a,b,c;通常会占用3或4字节的存储空间(至少在x86上),这是因为它们的对齐要求是1字节。如果不是的话,你会怎么处理char str[] ="foo";?访问char始终是一个简单的fetch shift掩码,而访问int可以是fetch fetch merge或just fetch,这取决于它是否对齐。int在x86上有一个32位(4字节)的对齐方式,因为否则一个DWORD中会得到(比如)半个int,而另一个则会得到半个,这将需要两次查找。
编译器可以在结构中对齐成员,以在特定平台上实现最大性能。#pragma pack指令允许您控制该对齐。通常,为了获得最佳性能,您应该将其保留为默认值。如果需要将结构传递给远程机器,通常会使用#pragma pack 1排除任何不需要的对齐。
由于特定体系结构的性能,编译器可以将结构成员放在特定的字节边界上。这可能会在成员之间留下未使用的填充。结构填料迫使构件相邻。
这可能很重要,例如,如果您需要一个符合特定文件或通信格式的结构,而您需要的数据在序列中的特定位置。然而,这样的用法并不处理endian ness问题,因此尽管使用了,它可能不可移植。
它还可以精确地覆盖一些I/O设备(例如UART或USB控制器)的内部寄存器结构,以便寄存器访问通过结构而不是直接地址。
我见过有人使用它来确保一个结构需要一整条缓存线来防止在多线程上下文中的错误共享。如果有大量的对象在默认情况下将被松散地打包,那么它可以节省内存并提高缓存性能以将它们打包得更紧密,尽管未对齐的内存访问通常会降低速度,因此可能会有不利的一面。
我以前在代码中使用过它,不过只是为了与遗留代码进行接口。这是一个Mac OS X Cocoa应用程序,需要从早期的Carbon版本加载首选项文件(它本身向后兼容原始的M68K System 6.5版本……你知道了)。原始版本中的首选项文件是配置结构的二进制转储文件,它使用#pragma pack(1)避免占用额外的空间并节省垃圾(即结构中可能存在的填充字节)。
代码的原始作者还使用#pragma pack(1)来存储在进程间通信中用作消息的结构。我认为这里的原因是为了避免出现未知或更改填充大小的可能性,因为代码有时会从一开始(ewww)开始对消息结构的特定部分进行计数,从而查看填充大小。
如果您对某些硬件(例如内存映射设备)进行编码,而这些硬件对寄存器排序和对齐有严格的要求,那么您可能只想使用这个。
然而,这看起来像是实现这一目标的一个非常钝的工具。一个更好的方法是在汇编程序中编写一个小型驱动程序的代码,并给它一个C调用接口,而不是用这个pragma瞎混。
- 实际上,我经常使用它来节省不经常访问的大表中的空间。在那里,它只是为了节省空间,而不是为了任何严格的对齐。(刚刚投票给你了,顺便说一句,有人投了你反对票。)
请注意,pragma pack提供了其他实现数据一致性的方法(例如,有些人使用pragma pack(1)来处理应该通过网络发送的结构)。例如,请参见以下代码及其后续输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #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
", sizeof(struct a ), sizeof(struct b ));
printf("sizeof(twoa): %i, sizeof(twob): %i
", sizeof(twoa ), sizeof(twob ));
} |
输出如下:sizeof(结构A):15,sizeof(结构B):24尺寸(twoa):30,尺寸(twob):48
请注意,结构A的大小与字节计数的大小完全相同,但结构B添加了填充(有关填充的详细信息,请参阅此部分)。与pragma包相反,通过这样做,您可以控制将"线格式"转换为适当的类型。例如,"char two[2]"变为"short int"等。
- 不,这是不对的。如果你看一下b.2在内存中的位置,它不是b.1之后的一个字节(编译器可以(并且经常)对齐b.2,这样它就与word access对齐了)。对于A.2,它正好是A.1之后的一个字节。如果您需要访问a.2作为一个短整型,那么您应该有两种选择,要么使用一个联合(但如果您有endianness问题,这通常会失败),要么使用代码解包/转换(使用适当的ntohx函数)