为啥 GCC 不优化结构?

Posted

技术标签:

【中文标题】为啥 GCC 不优化结构?【英文标题】:Why doesn't GCC optimize structs?为什么 GCC 不优化结构? 【发布时间】:2010-09-12 04:42:35 【问题描述】:

系统要求某些原语与内存中的某些点对齐(整数到 4 的倍数的字节,短到 2 的倍数的字节等)。当然,这些可以优化为在填充中浪费最少的空间。

我的问题是为什么 GCC 不自动执行此操作?是否缺少更明显的启发式(从最大尺寸要求到最小尺寸的顺序变量)?某些代码是否依赖于其结构的物理顺序(这是个好主意)?

我之所以这么问,是因为 GCC 在很多方面都进行了超级优化,但在这一方面却没有,而且我认为一定有一些相对酷的解释(我没有注意到)。

【问题讨论】:

您可以尝试 struct-reorg-branch 中的 -fipa-struct-reorg 选项。 Is there a GCC keyword to allow structure-reordering? 【参考方案1】:

您可能想尝试最新的 gcc 主干或正在积极开发的 struct-reorg-branch。

https://gcc.gnu.org/wiki/cauldron2015?action=AttachFile&do=view&target=Olga+Golovanevsky_+Memory+Layout+Optimizations+of+Structures+and+Objects.pdf

【讨论】:

【参考方案2】:

并不是说这是一个好主意,但您当然可以编写依赖于结构成员顺序的代码。例如,作为 hack,人们通常将指向结构的指针转换为他们想要访问的内部某个字段的类型,然后使用指针算术来到达那里。对我来说,这是一个非常危险的想法,但我已经看到它被使用过,特别是在 C++ 中,当它位于来自 3rd 方库的类中并且未公开封装时,强制一个已被声明为私有的变量可公开访问。重新排序成员将完全打破这一点。

【讨论】:

我相信 linux 内核会为链表做到这一点。【参考方案3】:

gcc 不会重新排序结构的元素,因为这会违反 C 标准。 C99 标准第 6.7.2.1 节规定:

在结构对象中,非位域成员和位域所在的单元 驻留的地址按照声明的顺序递增。

【讨论】:

是的,但为什么要这样定义? @nes1983 程序员可能会假设结构中数据的顺序,并且可能会使用掩码来获取每个部分。如果结构被重新排序而不是屏蔽我是不正确的。 @Evo510:我很困惑。要使用掩码,您还必须知道填充,语言不能保证这一点。所以,你不能使用面具。我错过了什么吗? @nes1983 我见过数值积分代码,它假设所有输入都是按顺序排列的浮点数。您将指针传递给要积分的第一个值和最后一个值,然后它会在它们之间进行扫描。但是,您将信息保存在结构中,因为对于除集成之外的所有内容,它是一种更方便的格式。 虽然它会违反标准,但有一种有用的重新排序方法可以保护 Linux 内核免受 rootkits/exploits:Linux KSPP (kernsec.org/wiki/index.php/Kernel_Self_Protection_Project) 的一部分是一些结构字段随机化/重新排序:openwall.com/lists/kernel-hardening/2017/05/26/8 (引入结构布局随机化插件),相关论文:sec.taylor.edu/doc/…(“通过内存布局随机化提高内核安全性” - DM Stanley - ‎2013)【参考方案4】:

gcc SVN 确实有结构重组优化(-fipa-struct-reorg),但需要全程序分析,目前还不是很强大。

【讨论】:

Stock gcc 10 年后(版本 7.2,由 Ubuntu 17.10 打包)未在手册页中记录此选项。奇怪的是,选项字符串被 gcc 可执行文件识别。【参考方案5】:

在从源代码生成机器代码方面,GCC 比我们大多数人更聪明;但是,如果它在重新安排我们的结构时比我们更聪明,我会颤抖,因为它是数据,例如可以写入文件。如果在 GCC 决定它应该重新排列结构成员的另一个系统上读取,以 4 个字符开头然后具有 4 个字节整数的结构将毫无用处。

【讨论】:

由于对齐(这是允许的),直接向文件读取/写入结构不是编译器/平台可移植的,请参阅this SO answer。【参考方案6】:

C 编译器不会自动打包结构,正是因为您提到的对齐问题。不在字边界(大多数 CPU 上为 32 位)上的访问会对 x86 造成严重的惩罚,并在 RISC 架构上造成致命的陷阱。

【讨论】:

我不是在谈论摆脱缓冲,我是在谈论将所有长/指针首尾相连,然后将所有短裤首尾相连,然后是所有字符端到端等,这样你只会在最后失去空间。 嗯,说对了一半。 C 编译器将默认打包它们,它们只是将其与体系结构的自然字边界对齐。这就是为什么您需要 #pragma pack(0) 在打包协议中使用 chars/shorts 的结构,以阻止它添加填充。 @Alex,错了。您将浪费相同数量的空间,因为您的角色必须填充相同数量。无论是空间还是性能方面,您都不会受益。 哦。是的,正如 Cody 所证明的那样,这会导致二进制格式出现问题。此外,ANSI 保证结构元素偏移量必须按递增顺序排列。 正确安排结构不会失去填充的任何好处。使用 short, char, char,您可以有 0 填充,但所有元素都落在正确的偏移量上。一般来说,你不会因此而失去任何速度,因为它们会落在它们的自然范围内【参考方案7】:

结构经常被用来表示二进制文件格式和网络协议的打包顺序。如果这样做,这将打破。此外,不同的编译器会以不同的方式优化事物,将两者的代码链接在一起是不可能的。这根本不可行。

【讨论】:

这与网络或文件结构无关。确实,BMP 结构的标头由落在非自然边界上的元素紧紧地挤在一起,这些元素对编译器来说是陌生的。 错了,是吗?你误解了这个问题。重读第二段,他谈到了结构排序。这与填充完全不同。 您的第一点非常有效。但我认为你的第二个不是。来自不同编译器的编译代码无论如何都不兼容。 @JohannesSchaub-litb 取决于;如果两个编译器都遵循相同的 ABI,那么它们就没有理由生成不兼容的代码。例如 GCC 和 Clang,以及 Windows 上用于 C 的 32 位 GCC 和 MSVC。

以上是关于为啥 GCC 不优化结构?的主要内容,如果未能解决你的问题,请参考以下文章

为啥检查没有被优化

为啥在 GCC 5.1 中仍然启用 COW std::string 优化?

为啥 GCC 没有尽可能地优化这组分支和条件?

GCC 不优化未初始化的静态常量的结构副本

为啥在编译期间不使用 GCC 选项 -Os?

为啥在 gcc 和 clang 上通过优化将大 double 转换为 uint16_t 会给出不同的答案