#pragma pack效果


233

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

我检查了MSDN页面,该页面提供了一些见解,但我希望能从有经验的人那里听到更多。我以前在代码中已经看到过,尽管我似乎再也找不到了。


1
它强制对结构进行特定的对齐/打包,但是像所有#pragma指令一样,它们是实现定义的。
dreamlax

A mod s = 0其中A是地址,s是数据类型的大小;这将检查数据是否未对齐。
legends2k

Answers:


422

#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中变量的顺序也很重要。变量排序如下:

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。


76
可能需要增加包装的缺点。(未对齐的对象访问在最佳情况下是缓慢的,但会在某些平台上导致错误。)
jalf 2010年

11
似乎在某些系统danluu.com/3c-conflict上提到的对齐“性能损失”实际上可能是有益的。

6
@Pacerier并非如此。该文章讨论了一些相当极端的对齐方式(在4KB边界上对齐)。CPU期望各种数据类型具有某些最小对齐方式,但是在最坏的情况下,这些对齐方式需要8字节对齐方式(不计算可能需要16或32字节对齐方式的矢量类型)。通常,在这些边界上不对齐会给您带来明显的性能下降(因为可能必须将负载作为两个操作而不是一个操作来完成),但是类型不是很好就对齐了。比这更严格的对齐不会给您带来任何好处(并且会浪费缓存利用率
2015年

6
换句话说,双精度数期望在8字节边界上。将其放在7字节边界上会损害性能。但是,将其放在16、32、64或4096字节边界上并不会比8字节边界给您带来的收益高。您会从CPU中获得相同的性能,同时由于该帖子中概述的原因,缓存利用率将大大降低。
2015年

4
所以教训是不是“包装是有益的”(包装违反了类型的自然对齐,这样会降低性能),而只是‘不要过分超出对齐需要什么’
jalf

27

#pragma用于将非便携式(仅在此编译器中)消息发送到编译器。诸如禁用某些警告和打包结构之类的事情是常见的原因。如果在打开错误标志时编译警告,则禁用特定警告特别有用。

#pragma pack特别是用于指示正​​在打包的结构不应使其成员对齐。当您具有到硬件的内存映射接口并且需要能够精确控制不同结构成员指向的位置时,此功能很有用。值得注意的是,这不是一个很好的速度优化,因为大多数计算机处理对齐数据的速度要快得多。


17
要在以后撤消操作,请执行以下操作:#pragma pack(push,1)和#pragma pack(pop)
malhal 2014年

16

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

struct foo { 
    char a;
    int b;
};

对于典型的32位计算机,通常会“希望”在a和之间添加3个字节的填充,b以便b将其降落在4个字节的边界以最大化其访问速度(默认情况下通常会发生这种情况)。

但是,如果必须匹配外部定义的结构,则要确保编译器完全根据该外部定义对结构进行布局。在这种情况下,您可以给编译器提供一个命令#pragma pack(1),告诉它不要在成员之间插入任何填充-如果结构的定义包括成员之间的填充,则可以显式地插入它(例如,通常使用名为unusedN或的成员ignoreN,或在其上添加一些东西订购)。


“您通常”希望”在a和b之间具有3个字节的填充,以便b将落在4个字节的边界上以最大化其访问速度”-具有3个字节的填充将如何最大程度地提高访问速度?
2014年

8
@Ashwin:放置b在4字节边界意味着处理器可以通过发出单个4字节加载来加载它。尽管它在某种程度上取决于处理器,但是如果它处在一个奇怪的边界,则很有可能加载它需要处理器发出两个单独的加载指令,然后使用移位器将这些片段放在一起。典型的惩罚是该项目的负载减慢3倍。
杰里·科芬

...如果查看汇编代码以读取已对齐和未对齐的int,则对齐读取通常是单个助记符。未对齐的读取可以很容易地进行10行汇编,因为它将int拼凑在一起,逐个字节地拾取它,并将其放置在寄存器的正确位置。
SF。

2
@SF .:可以-但即使不是这样,也不要误导-在x86 CPU上(举一个明显的例子),这些操作是在硬件中执行的,但是您仍然可以获得大致相同的一组操作和减速。
杰里·科芬

8

数据元素(例如,类和结构的成员)通常在当前处理器的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";?访问a char始终是简单的fetch-shift-mask,而对a的访问int可以是fetch-fetch-merge或只是fetch,具体取决于它是否对齐。int(在x86上)具有32位(4字节)对齐方式,因为否则您将得到(比如说)一半一半int,另一DWORD半一半,这将需要两次查找。
TimČas2012年

3

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


2

出于性能原因,编译器可能会将结构成员放在特定的字节边界上。这可能会在成员之间留下未使用的填充。结构打包迫使成员是连续的。

例如,如果您需要一种结构来符合特定文件或通信格式,则其中的数据需要位于序列中的特定位置,则这可能很重要。但是,这种用法不能解决字节序问题,因此尽管使用了它,但可能不是便携式的。

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


1

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

但是,这似乎是实现该目标的一种非常钝的工具。更好的方法是在汇编器中编写一个微型驱动程序,并为其提供C调用接口,而不是为此而烦恼。


我实际上经常使用它来节省大表中不经常访问的空间。在那里,这只是为了节省空间,而不是为了任何严格的对齐。(顺便说一句,您投了赞成票,有人给了您反对票。)
Todd Lehman

1

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

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


1

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


0

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

请注意一个结构的大小是如何准确的字节数是什么,但结构B已经添加填充(见关于填充细节)。与#pragma包相反,通过执行此操作,您可以控制将“有线格式”转换为适当的类型。例如,将“ char two [2]”转换为“ short int”等。


不,这是错误的。如果您查看b.two在内存中的位置,它不是b.one之后的一个字节(编译器可以(并且经常)将b.two对齐,从而使其与字访问对齐。对于a.two,它恰好是a.one之后的一个字节。如果您需要将a.two作为短整数进行访问,则应该有2种选择,要么使用并集(但是如果遇到字节序问题通常会失败),或者通过代码解压缩/转换(使用适当的ntohX函数)
xryl669

1
sizeof返回一个size_t其中必须使用被打印出来%zu。使用错误的格式说明符会
导致

0

为什么要使用它?

减少结构的记忆

为什么不应该使用它?

  1. 这可能会导致性能下降,因为某些系统在对齐的数据上可以更好地工作
  2. 某些机器将无法读取未对齐的数据
  3. 代码不可移植
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.