#pragma 包效果

Posted

技术标签:

【中文标题】#pragma 包效果【英文标题】:#pragma pack effect 【发布时间】:2011-03-20 02:31:39 【问题描述】:

我想知道是否有人可以向我解释 #pragma pack 预处理器语句的作用,更重要的是,为什么要使用它。

我查看了MSDN page,它提供了一些见解,但我希望从有经验的人那里听到更多信息。我以前在代码中见过它,但我似乎无法再找到它。

【问题讨论】:

它强制对结构进行特定的对齐/打包,但与所有 #pragma 指令一样,它们是由实现定义的。 【参考方案1】:

如果您对某些对寄存器排序和对齐有严格要求的硬件(例如内存映射设备)进行编码,您可能只想使用它。

但是,这看起来是一个非常生硬的工具来实现这一目标。更好的方法是在汇编程序中编写一个微型驱动程序并为其提供一个 C 调用接口,而不是摸索这个 pragma。

【讨论】:

我实际上经常使用它来节省不经常访问的大表中的空间。在那里,它只是为了节省空间,而不是为了任何严格的对齐。 (顺便说一句,刚刚投了你一票。有人给你投了反对票。)【参考方案2】:

它告诉编译器将结构中的对象对齐到的边界。例如,如果我有类似的东西:

struct foo  
    char a;
    int b;
;

对于典型的 32 位机器,您通常“希望”在 ab 之间有 3 个字节的填充,以便 b 将位于 4 字节边界以最大化其访问速度(这就是默认情况下通常会发生的情况)。

但是,如果您必须匹配一个外部定义的结构,您希望确保编译器完全按照该外部定义布置您的结构。在这种情况下,您可以给编译器一个#pragma pack(1) 告诉它不要在成员之间插入任何填充——如果结构的定义包括成员之间的填充,则显式插入它(例如,通常与名为unusedNignoreN 的成员,或类似的顺序)。

【讨论】:

“你通常会“希望”在 a 和 b 之间有 3 个字节的填充,以便 b 将落在一个 4 字节的边界以最大化其访问速度” - 如何拥有 3 个字节的填充最大化访问速度? @Ashwin:将b 放置在 4 字节边界意味着处理器可以通过发出单个 4 字节加载来加载它。尽管它在某种程度上取决于处理器,但如果它处于奇怪的边界,则很有可能加载它需要处理器发出两个单独的加载指令,然后使用移位器将这些部分放在一起。典型的惩罚是该项目的加载速度慢 3 倍。 ...如果您查看用于读取对齐和未对齐 int 的汇编代码,对齐读取通常是单个助记符。未对齐读取可以很容易地进行 10 行汇编,因为它将 int 拼凑在一起,逐字节拾取并放置在寄存器的正确位置。 @SF.:它可以——但即使不是,也不要被误导——在 x86 CPU(举一个明显的例子)上,操作是在硬件中执行的,但是你仍然得到大致相同的操作和减速。【参考方案3】:

#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。

结构中变量的顺序也很重要。变量排序如下:

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。

【讨论】:

可能值得添加包装的缺点。 (未对齐的对象访问在最佳情况下很慢,但在某些平台上会导致错误。) 似乎提到的对齐“性能损失”实际上可能对某些系统有好处danluu.com/3c-conflict。 @Pacerier 不是。那篇文章谈到了一些相当极端的对齐方式(在 4KB 边界上对齐)。 CPU 期望各种数据类型有一定的最小对齐,但在最坏的情况下需要 8 字节对齐(不包括可能需要 16 或 32 字节对齐的向量类型)。不在这些边界上对齐通常会给您带来明显的性能影响(因为加载可能必须作为两个操作而不是一个操作来完成),但类型要么对齐要么不对齐。比这更严格的对齐不会给你带来任何好处(并且会破坏缓存利用率 换句话说,double 期望在 8 字节边界上。将其放在 7 字节边界上会损害性能。但是将它放在 16、32、64 或 4096 字节的边界上并不会超过 8 字节边界已经给你的东西。您将从 CPU 获得相同的性能,同时由于该帖子中所述的原因而获得更差的缓存利用率。 所以教训不是“打包是有益的”(打包违反了类型的自然对齐,因此会损害性能),而只是“不要过度对齐超出要求”【参考方案4】:

数据元素(例如类和结构的成员)通常在当前一代处理器的 WORD 或 DWORD 边界上对齐,以缩短访问时间。在一个不能被 4 整除的地址上检索一个 DWORD 需要在 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-mask,而访问 int 可以是 fetch-fetch-merge 或只是 fetch,这取决于它是否对齐。 int (在 x86 上)具有 32 位(4 字节)对齐方式,因为否则你会在一个 DWORD 中得到(比如说)一半 int 而在另一个中得到一半,这需要两次查找。 【参考方案5】:

编译器可能将结构成员放置在特定的字节边界上,以提高特定体系结构的性能。这可能会在成员之间留下未使用的填充。结构填料迫使成员是连续的。

这可能很重要,例如,如果您需要结构符合特定文件或通信格式,其中数据需要位于序列中的特定位置。然而,这种用法并不能处理字节序问题,因此虽然使用过,但它可能不可移植。

它还可以精确覆盖某些 I/O 设备(例如 UART 或 USB 控制器)的内部寄存器结构,以便通过结构而不是直接地址访问寄存器。

【讨论】:

【参考方案6】:

编译器可以对齐结构中的成员以在特定平台上实现最大性能。 #pragma pack 指令允许您控制对齐方式。通常,您应该默认保留它以获得最佳性能。如果您需要将结构传递给远程机器,您通常会使用#pragma pack 1 来排除任何不需要的对齐。

【讨论】:

【参考方案7】:

#pragma 用于向编译器发送不可移植的(仅在此编译器中)消息。诸如禁用某些警告和打包结构之类的事情是常见的原因。如果您在打开错误标志时使用警告进行编译,则禁用特定警告特别有用。

#pragma pack 专门用于指示被打包的结构不应使其成员对齐。当您有一个到硬件的内存映射接口并且需要能够准确控制不同结构成员指向的位置时,它很有用。这显然不是一个很好的速度优化,因为大多数机器在处理对齐数据时要快得多。

之后要撤消包装#pragma pack(push,1)#pragma pack(pop)

【讨论】:

之后要撤消,请执行以下操作:#pragma pack(push,1) 和 #pragma pack(pop) @malhal 这应该是答案的一部分。我来这里就是为了找这个 @MANA624 谢谢我在答案中添加了我的评论【参考方案8】:

我以前在代码中使用过它,但只是为了与遗留代码交互。这是一个 Mac OS X Cocoa 应用程序,需要从更早的 Carbon 版本加载首选项文件(它本身向后兼容原始 M68k System 6.5 版本……你懂的)。原始版本中的首选项文件是配置结构的二进制转储,使用 #pragma pack(1) 以避免占用额外空间并节省垃圾(即结构中的填充字节)。

代码的原始作者还使用#pragma pack(1) 来存储在进程间通信中用作消息的结构。我认为这里的原因是为了避免填充大小未知或更改的可能性,因为代码有时会通过从一开始就计算字节数(ewww)来查看消息结构的特定部分。

【讨论】:

【参考方案9】:

我看到人们使用它来确保结构占用整个缓存行以防止在多线程上下文中进行错误共享。如果您将拥有大量默认松散打包的对象,则可以节省内存并提高缓存性能以将它们打包得更紧密,尽管未对齐的内存访问通常会减慢速度,因此可能会有不利之处。

【讨论】:

【参考方案10】:

请注意,#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));

输出如下: 大小(结构 a):15,大小(结构 b):24 sizeof(twoa): 30, sizeof(twob): 48

请注意 struct a 的大小与字节数完全相同,但 struct b 添加了填充(有关填充的详细信息,请参阅this)。通过这样做而不是#pragma 包,您可以控制将“有线格式”转换为适当的类型。例如,“char two[2]”变成“short int”等等。

【讨论】:

不,这是错误的。如果您查看 b.two 在内存中的位置,它不是 b.one 之后的一个字节(编译器可以(并且经常)对齐 b.two,因此它与字访问对齐)。对于 a.two,它恰好是 a.one 之后的一个字节。如果您需要将 a.two 作为短 int 访问,您应该有 2 种选择,或者使用联合(但如果您有字节序问题,这通常会失败),或者通过代码解包/转换(使用适当的 ntohX 函数)跨度> sizeof 返回一个size_t,其中must be printed out using %zu。使用错误的格式说明符会调用未定义的行为【参考方案11】:

为什么要使用它?

减少结构体的内存

为什么不应该使用它?

    这可能会导致性能下降,因为某些系统在对齐数据上效果更好 某些机器将无法读取未对齐的数据 代码不可移植

【讨论】:

以上是关于#pragma 包效果的主要内容,如果未能解决你的问题,请参考以下文章

#pragma预处理命令详解

#pragma mark指令

pragma comment的使用

#pragma pack(show) 与 GCC

#pragma 标记的意义​​是啥?为啥我们需要#pragma 标记?

#pragma 警告(push) 没有 #pragma 警告(pop)