gcc 结构中的内存对齐

Posted

技术标签:

【中文标题】gcc 结构中的内存对齐【英文标题】:memory alignment within gcc structs 【发布时间】:2011-02-02 23:56:16 【问题描述】:

我正在用 C 语言将应用程序移植到 ARM 平台,该应用程序也在 x86 处理器上运行,并且必须向后兼容。

我现在在变量对齐方面遇到了一些问题。我已经阅读了 gcc 手册 __attribute__((aligned(4),packed)) 我将所说的解释为结构的开头与 4 字节边界对齐,并且由于打包语句,内部保持不变。

最初我有这个,但偶尔它会与 4 字节边界不对齐。

typedef struct  
  
 unsigned int code;  
 unsigned int length;  
 unsigned int seq;  
 unsigned int request;  
 unsigned char nonce[16];  
 unsigned short  crc;  
 __attribute__((packed)) CHALLENGE;

所以我把它改成这个。

typedef struct  
  
 unsigned int code;  
 unsigned int length;  
 unsigned int seq;  
 unsigned int request;  
 unsigned char nonce[16];  
 unsigned short  crc;  
 __attribute__((aligned(4),packed)) CHALLENGE;

我之前所说的理解似乎是不正确的,因为结构现在与 4 字节边界对齐,内部数据现在与 4 字节边界对齐,但由于字节序,结构的大小大小从 42 字节增加到 44 字节。这个大小很关键,因为我们有其他应用程序依赖于 42 字节的结构。

可以向我描述如何执行我需要的操作。非常感谢任何帮助。

【问题讨论】:

【参考方案1】:

如果您依赖 sizeof(yourstruct) 为 42 个字节,那么您将被一个不可移植的假设世界所困扰。您还没有说这是为了什么,但结构内容的字节顺序似乎也很重要,因此您也可能与那里的 x86 不匹配。

在这种情况下,我认为唯一可靠的应对方法是在重要的部分使用unsigned char[42]。首先编写一个精确的规范,说明这个 42 字节块中的确切字段是什么,以及什么字节序,然后使用该定义编写一些代码来在它和您可以交互的结构之间进行转换。代码可能是一次性序列化代码(也称为编组),或者是一堆 getter 和 setter。

【讨论】:

虽然我同意其他所有观点,但我不确定您为什么建议使用 char 数组。 @Roger:我假设 OP 需要以强制形式以及更容易操作的形式将结构保存在内存中 - 除非您要提出其他观点我错过了什么? @crazy:OP 显然可以很好地使用压缩结构来表示数据文件的内存中表示,这使得使用 char 数组等同于使用 &struct_obj 作为 char 数组(通过将其转换为 char 指针)并且仅使用前 42 个字节。如果他想放弃包装,那么可能有必要——暂时。但即使在这种情况下,我也只会使用缓冲操作(例如 FILE)并读取每个成员。 数据结构本质上是一个数据包,在发送之前我确保在相关成员上使用 htonl/htons,我认为编组将是正确的选择。我将看看实现起来有多么容易,因为大约有 100 个类似的结构。非常感谢您的回复 @Mumbles:如果您可以使用 C++ 代替 C,您可以通过为每个结构编写一小段代码来完成它(类似于 boost::serialize 的工作方式)。否则(甚至在 C++ 中,视情况而定),我会为您的结构生成代码,这样您就可以使用相同的输入文件来生成序列化函数并始终知道它们是同步的。【参考方案2】:

这是读取整个结构而不是按成员读取失败的原因之一,应该避免。

在这种情况下,packing plus aligning at 4 意味着会有两个字节的填充。发生这种情况是因为大小必须与将类型存储在数组中兼容,并且所有项目仍以 4 对齐。

我想你有类似的东西:

read(fd, &obj, sizeof obj)

因为您不想读取属于不同数据的那 2 个填充字节,所以您必须明确指定大小:

read(fd, &obj, 42)

您可以保持可维护性:

typedef struct 
  //...
  enum  read_size = 42 ;
 __attribute__((aligned(4),packed)) CHALLENGE;

// ...

read(fd, &obj, obj.read_size)

或者,如果你不能在你的 C 语言中使用 C++ 的某些特性:

typedef struct 
  //...
 __attribute__((aligned(4),packed)) CHALLENGE;
enum  CHALLENGE_read_size = 42 ;

// ...

read(fd, &obj, CHALLENGE_read_size)

在下一次重构机会时,我强烈建议您开始单独阅读每个成员,这可以很容易地封装在一个函数中。

【讨论】:

【参考方案3】:

我一直在从 Linux、Windows、Mac、C、Swift、Assembly 等中来回移动结构。

问题不在于做不到,问题在于你不能偷懒,必须了解你的工具。

我不明白你为什么不能使用:

typedef struct  
  
 unsigned int code;  
 unsigned int length;  
 unsigned int seq;  
 unsigned int request;  
 unsigned char nonce[16];  
 unsigned short  crc;  
 __attribute__((packed)) CHALLENGE;

可以使用它,并且不需要任何特殊或聪明的代码。我写了很多与 ARM 通信的代码。结构是使事情起作用的原因。 __attribute__ ((packed))是我的朋友。

如果您了解两者的情况,那么进入“受伤害的世界”的可能性为零。

最后,我无法确定你是如何得到 42 或 44 的。Int 是 4 或 8 字节(取决于编译器)。这使得数字为 16+16+2=34 或 32+16+2=50 —— 假设它真的被打包了。

正如我所说,了解你的工具是你问题的一部分。

【讨论】:

将 uint32_t 用于无符号整数和 uint32_t 用于无符号短裤更安全。 毫无疑问,您的意思是 uint16_t 用于未签名的短裤。 对“更安全”感到困惑。你的意思是它不会混淆字节数。最重要的是,如果您不了解您的工具,不知道字节数等。您将崩溃和烧毁。至于int32_t,是的,那比int好。因为 int16_t 比 short 好。 (或 uintxx_t 取决于符号是否有问题) 是的,我的意思是 uint16_t 用于未签名的短裤。我所说的更安全的意思是,如果你在几台不同的计算机(比如 16 位机器、32 位机器和 64 位机器)之间传递这个结构,对于 unsigned int/unsigned,它们中的每一个都可以有不同的长度短的。因为 c++ 不对大小做任何保证。这使得结构在多台机器之间无用。【参考方案4】:

你真正的目标是什么?

如果要处理文件中或网络上特定格式的数据,您应该编写一些编组/序列化例程,在编译器结构之间移动数据,表示您希望如何处理程序内部的数据和一个处理数据在线路/文件上的外观的 char 数组。

那么,所有需要仔细处理并且可能具有特定于平台的代码的就是编组例程。而且您可以编写一些不错的单元测试,以确保无论您现在和将来需要移植到什么平台,封送数据都能正确地进出结构体。

【讨论】:

这个结构的目标是网络数据包。我非常喜欢有一个由编译器对齐的内部结构,以便它正确匹配,然后只在需要时构造这个数据包。【参考方案5】:

我猜问题在于 42 不能被 4 整除,因此如果您将其中几个结构体背靠背放置(例如为其中几个结构体分配内存,使用 @ 确定大小),它们就会失去对齐987654321@)。在这些情况下,大小为 44 会按照您的要求强制对齐。但是,如果每个结构成员的内部偏移量保持不变,您可以将 44 字节结构视为 42 字节(只要您注意在正确的边界对齐任何后续数据)。

尝试的一个技巧可能是将这些结构两个放在一个联合类型中,并且只使用每个联合类型中的 42 字节版本。

【讨论】:

请注意,这种“背靠背”分配在数组中自动发生,这就是为什么类型的大小必须包含那些填充字节以保持对齐。你不能用任何技巧改变数组布局,我也不建议使用它们。【参考方案6】:

当我使用 linux 时,我发现 echo 3 > /proc/cpu/alignment 会向我发出警告,并修复对齐问题。这是一种变通方法,但它对于定位结构未对齐的位置非常有帮助。

【讨论】:

以上是关于gcc 结构中的内存对齐的主要内容,如果未能解决你的问题,请参考以下文章

内存字节对齐

GCC 向量扩展和 ARM NEON 的内存对齐问题

使用 GCC/G++/AS 在固定大小的内存边界上对齐本机代码?

gcc数据对齐之: howto 2.

预取对齐内存

内存对齐:C/C++编程中的重要性和技巧