Answers:
#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
特别是用于指示正在打包的结构不应使其成员对齐。当您具有到硬件的内存映射接口并且需要能够精确控制不同结构成员指向的位置时,此功能很有用。值得注意的是,这不是一个很好的速度优化,因为大多数计算机处理对齐数据的速度要快得多。
它告诉编译器边界以使结构中的对象对齐。例如,如果我有类似的东西:
struct foo {
char a;
int b;
};
对于典型的32位计算机,通常会“希望”在a
和之间添加3个字节的填充,b
以便b
将其降落在4个字节的边界以最大化其访问速度(默认情况下通常会发生这种情况)。
但是,如果必须匹配外部定义的结构,则要确保编译器完全根据该外部定义对结构进行布局。在这种情况下,您可以给编译器提供一个命令#pragma pack(1)
,告诉它不要在成员之间插入任何填充-如果结构的定义包括成员之间的填充,则可以显式地插入它(例如,通常使用名为unusedN
或的成员ignoreN
,或在其上添加一些东西订购)。
b
在4字节边界意味着处理器可以通过发出单个4字节加载来加载它。尽管它在某种程度上取决于处理器,但是如果它处在一个奇怪的边界,则很有可能加载它需要处理器发出两个单独的加载指令,然后使用移位器将这些片段放在一起。典型的惩罚是该项目的负载减慢3倍。
数据元素(例如,类和结构的成员)通常在当前处理器的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
半一半,这将需要两次查找。
如果您要对某些对寄存器排序和对齐有严格要求的硬件(例如,内存映射设备)进行编码,则可能只想使用此功能。
但是,这似乎是实现该目标的一种非常钝的工具。更好的方法是在汇编器中编写一个微型驱动程序,并为其提供C调用接口,而不是为此而烦恼。
我以前在代码中使用过它,虽然只是为了与遗留代码交互。这是一个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
请注意一个结构的大小是如何准确的字节数是什么,但结构B已经添加填充(见本关于填充细节)。与#pragma包相反,通过执行此操作,您可以控制将“有线格式”转换为适当的类型。例如,将“ char two [2]”转换为“ short int”等。
#pragma
指令一样,它们是实现定义的。