我想知道是否有人可以向我解释#pragma pack
预处理器语句的作用,更重要的是,为什么要使用它。
我查看了MSDN 页面,它提供了一些见解,但我希望从有经验的人那里听到更多。我以前在代码中见过它,但我似乎再也找不到在哪里了。
我想知道是否有人可以向我解释#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。
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。
#pragma
用于向编译器发送不可移植(仅在此编译器中)消息。诸如禁用某些警告和打包结构之类的事情是常见的原因。如果您在打开错误标志时使用警告进行编译,则禁用特定警告特别有用。
#pragma pack
具体用于指示被打包的结构不应使其成员对齐。当您有一个到硬件的内存映射接口并且需要能够准确控制不同结构成员指向的位置时,它很有用。这显然不是一个很好的速度优化,因为大多数机器在处理对齐的数据方面要快得多。
之后撤消换行#pragma pack(push,1)
和#pragma pack(pop)
它告诉编译器将结构中的对象对齐到的边界。例如,如果我有类似的东西:
struct foo {
char a;
int b;
};
对于典型的 32 位机器,您通常“希望”在 和 之间有 3 个字节的填充a
,b
因此b
它将落在 4 字节边界以最大化其访问速度(默认情况下通常会发生这种情况)。
但是,如果您必须匹配外部定义的结构,您希望确保编译器完全根据该外部定义来布置您的结构。在这种情况下,您可以给编译器 a#pragma pack(1)
告诉它不要在成员之间插入任何填充 - 如果结构的定义包括成员之间的填充,则显式插入它(例如,通常使用名为unusedN
or的成员ignoreN
,或上面的东西命令)。
数据元素(例如类和结构的成员)通常在当前一代处理器的 WORD 或 DWORD 边界上对齐,以提高访问时间。在一个不能被 4 整除的地址上检索一个 DWORD 需要在 32 位处理器上至少一个额外的 CPU 周期。因此,如果您有例如三个 char 成员char a, b, c;
,它们实际上往往会占用 6 或 12 个字节的存储空间。
#pragma
允许您覆盖它以实现更有效的空间使用,但会牺牲访问速度或不同编译器目标之间存储数据的一致性。从 16 位代码到 32 位代码的转换让我很开心;我预计移植到 64 位代码会对某些代码造成同样的麻烦。
编译器可以对齐结构中的成员以在特定平台上实现最大性能。#pragma pack
指令允许您控制该对齐方式。通常,您应该默认保留它以获得最佳性能。如果您需要将结构传递给远程机器,您通常会使用它#pragma pack 1
来排除任何不需要的对齐。
出于特定体系结构的性能原因,编译器可以将结构成员放置在特定的字节边界上。这可能会在成员之间留下未使用的填充。结构填料迫使成员是连续的。
这可能很重要,例如,如果您需要结构符合特定文件或通信格式,其中您需要数据位于序列中的特定位置。然而,这样的用法并不能处理字节序问题,所以虽然使用过,但它可能不是可移植的。
它还可以精确地覆盖某些 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) 来表示应该通过网络发送的结构)。例如,请参阅以下代码及其后续输出:
#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 添加了填充(有关填充的详细信息,请参阅this)。通过这样做而不是#pragma 包,您可以控制将“有线格式”转换为适当的类型。例如,“char two[2]”变成“short int”等等。