Why doesn't GCC optimize structs?
系统要求某些基元与内存中的某些点对齐(整数到4的倍数字节,短于2的倍数字节等)。当然,这些可以被优化以在填充中浪费最少的空间。
我的问题是为什么海湾合作委员会不自动这样做?在某种程度上是否缺少更明显的启发式(从最大的大小需求到最小的顺序变量)?某些代码是否依赖于其结构的物理顺序(这是个好主意)?
我只是在问,因为GCC在很多方面都是超级优化的,但在这个方面没有,我想一定有一些相对酷的解释(我对此一无所知)。
- 您可以在struct reorg分支中尝试-fipa-struct-reorg选项。是否有允许结构重新排序的gcc关键字?
GCC不会对结构的元素重新排序,因为这将违反C标准。C99标准第6.7.2.1节规定:
Within a structure object, the non-bit-?eld members and the units in which bit-?elds
reside have addresses that increase in the order in which they are declared.
- 是的,但为什么要这样定义?
- @NES1983程序员可能对结构中的数据顺序进行假设,并可能使用屏蔽来获取每个部分。如果结构被重新排序,而不是屏蔽,那么我是不正确的。
- @我很困惑。要使用遮罩,您还必须知道填充,这是语言不能保证的。所以,你不能用面具。我错过什么了吗?
- @NES1983我见过数字积分代码,它假设所有的输入都是按顺序浮动的。将指针传递给要集成的第一个值,将最后一个值传递给它,它将在它们之间扫描。但是,您将信息保存在结构中,因为对于除集成之外的所有内容,它是一种更方便的格式。
- 虽然这将违反标准,但有一种有用的重新排序方法来保护Linux内核免受rootkit/漏洞攻击:Linux kspp的一部分(kernsec.org/wiki/index.php/kernel_self_protection_project)是一些结构字段随机化/重新排序:openwall.com/lists/kernel-hardence/2017/05/26/8(引入结构布局随机化插件),相关论文:sec.taylor.edu/doc/…("通过内存布局随机化提高内核安全性"-dm stanley-?2013)
结构经常被用来表示二进制文件格式和网络协议的打包顺序。如果这样做的话,这个问题就会被打破。此外,不同的编译器将以不同的方式优化问题,并且将代码从两者链接在一起是不可能的。这根本不可行。
- 这与网络或文件结构无关。事实上,BMP结构的头部被紧密地填充了元素,这些元素落在与编译器无关的非自然边界上。
- 呃,是吗?你误解了这个问题。重读第二段,他谈到结构排序。这与填充完全不同。
- 你的第一点是非常正确的。但我认为您的第二个不是。不同编译器编译的代码无论如何都不兼容。
- @这取决于johannesschoub-litb;如果两个编译器都遵循相同的ABI,就没有理由生成不兼容的代码。例如gcc和clang,以及32位gcc和msvc for c on windows。
GCC在从源代码生成机器代码方面比我们大多数人都聪明;但是,如果它在重新安排结构方面比我们聪明的话,我会发抖,因为它是可以写入文件的数据。如果在GCC决定应该重新安排结构成员的另一个系统上读取以4个字符开始,然后具有4个字节整数的结构,那么它将是无用的。
- 直接读/写结构到文件不是编译器/平台可移植的,因为对齐(这是允许的),请看这个答案。
GCCSVN确实有一个结构重组优化(fipa struct reorg),但是它需要整个程序分析,目前还不是很强大。
- 10年后的Stock GCC(7.2版,由Ubuntu 17.10打包)并未在手册页中记录此选项。奇怪的是,gcc可执行文件可以识别选项字符串。
不说这是个好主意,但您当然可以编写依赖于结构成员顺序的代码。例如,作为一个黑客,人们通常将指向结构的指针作为他们想要访问的内部某个字段的类型,然后使用指针算法到达那里。对我来说,这是一个非常危险的想法,但我见过它,特别是在C++中强制变量被声明为私有的,当它在一个第三方库中的一个类中被公开访问,而不是公开封装的。重新排序成员将完全打破这一点。
C编译器不会自动打包结构,这完全是因为您提到的对齐问题。不在字边界上访问(大多数CPU上为32位)会对x86造成严重惩罚,并在RISC架构上造成致命陷阱。
- 我不是说要摆脱缓冲,我是说要把所有的长/指针端到端,然后所有的短裤端到端,然后所有的字符端到端,等等,这样你只会在结尾处失去空间。
- 好吧,那是半真半假。C编译器将默认打包它们,它们只是按照体系结构的自然单词边界进行打包。这就是为什么您需要在打包协议中使用chars/short的pragma pack(0)结构来阻止它添加填充。
- "亚历克斯,呃。你会浪费同样的空间,因为你的角色必须被填充同样的数量。无论是空间还是性能,你都不会从中受益。
- 哦。是的,这会给二进制格式带来麻烦,科迪证明了这一点。此外,ANSI保证结构元素偏移量必须按递增顺序排列。
- @Cody,所有的空间都将浪费在最后,或者小于或等于常规的"按需分配"案例和要点:struct blah char a;short b;char c;struct blah2 short b;char a,c;blah比blah2浪费2个字节以上
- 因为一个字符可以介于另一个字符和短字符之间,而不是为每个字符浪费一个填充字符。
- 但事实并非如此。如果禁用填充,则会得到相同的大小,但会失去需要填充的原因——大多数体系结构根本无法访问它,而那些可以更慢地访问它的体系结构。所以你正在失去填充和重新排序以获得…同样的事情。
- 通过正确安排结构,填充不会失去任何好处。对于short、char、char,可以使用0填充,但所有元素都位于正确的偏移量上。一般来说,你不会因此而失去任何速度,因为它们会落在自己的自然边界上。
- 不,这在大多数架构中是不正确的。即使允许,访问不对齐的内存(通常是32位)也会受到惩罚,即使允许。
- 你可以保持单词对齐的记忆。字符可以放在任何字节上(显然)。短路可以在2的倍数的任何字节上启动。整数是4的倍数。给定字符a,b;short c;a b c c是有效的,但重新排列为a,c,b必须是a x c b x|
您可能想尝试最新的gcc主干,或者正在进行活动开发的structreorg分支。
https://gcc.gnu.org/wiki/couldron2015?action=attachfile&do=view&target=olga+golovanevasky_uux+memory+layout+optimizations+of+structures+and+objects.pdf