为什么 C 编译器不能重新排列结构成员以消除对齐填充? [英] Why can't C compilers rearrange struct members to eliminate alignment padding?

查看:18
本文介绍了为什么 C 编译器不能重新排列结构成员以消除对齐填充?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

可能的重复:
为什么 GCC 不优化结构?
为什么 C++ 不使结构更紧凑?

在 32 位 x86 机器上考虑以下示例:

Consider the following example on a 32 bit x86 machine:

由于对齐限制,下面的结构体

Due to alignment constraints, the following struct

struct s1 {
    char a;
    int b;
    char c;
    char d;
    char e;
}

如果成员像这样重新排序,则可以更高效地表示(12 对 8 字节)

could be represented more memory-efficiently (12 vs. 8 bytes) if the members were reordered as in

struct s2 {
    int b;
    char a;
    char c;
    char d;
    char e;
}

我知道 C/C++ 编译器不允许这样做.我的问题是为什么语言是这样设计的.毕竟,我们最终可能会浪费大量内存,而诸如 struct_ref->b 之类的引用不会在意差异.

I know that C/C++ compilers are not allowed to do this. My question is why the language was designed this way. After all, we may end up wasting vast amounts of memory, and references such as struct_ref->b would not care about the difference.

编辑:感谢大家提供非常有用的答案.您很好地解释了为什么由于语言的设计方式重新排列不起作用.然而,这让我想到:如果重新排列是语言的一部分,这些论点还会成立吗?假设有一些特定的重排规则,我们至少要求

EDIT: Thank you all for your extremely useful answers. You explain very well why rearranging doesn't work because of the way the language was designed. However, it makes me think: Would these arguments would still hold if rearrangement was part of the language? Let's say that there was some specified rearrangement rule, from which we required at least that

  1. 我们应该只在确实需要时重新组织结构(如果结构已经紧密",则不要做任何事情)
  2. 该规则仅查看结构的定义,而不查看内部结构的内部.这确保了结构类型无论是否在另一个结构内部都具有相同的布局
  3. 给定结构的已编译内存布局根据其定义是可预测的(即规则是固定的)

我一一解决你的论点:

  • 低级数据映射,最少惊喜的元素":只需自己以紧凑的风格编写结构(如@Perry 的回答),没有任何变化(要求 1).如果出于某种奇怪的原因,您想要内部填充,您可以使用虚拟变量手动插入它,和/或可能有关键字/指令.

  • Low-level data mapping, "element of least surprise": Just write your structs in a tight style yourself (like in @Perry's answer) and nothing has changed (requirement 1). If, for some weird reason, you want internal padding to be there, you could insert it manually using dummy variables, and/or there could be keywords/directives.

编译器差异:要求 3 消除了这种担忧.实际上,从@David Heffernan 的评论来看,我们今天似乎有这个问题,因为不同的编译器填充不同?

Compiler differences: Requirement 3 eliminates this concern. Actually, from @David Heffernan's comments, it seems that we have this problem today because different compilers pad differently?

优化:重新排序的重点是(内存)优化.我在这里看到了很多潜力.我们可能无法一起删除填充,但我看不出重新排序如何以任何方式限制优化.

Optimization: The whole point of reordering is (memory) optimization. I see lots of potential here. We may not be able to remove padding all together, but I don't see how reordering could limit optimization in any way.

类型转换:在我看来这是最大的问题.不过,应该有办法解决这个问题.由于规则在语言中是固定的,编译器能够弄清楚成员是如何重新排序的,并做出相应的反应.如上所述,在您想要完全控制的情况下,始终可以防止重新排序.此外,要求 2 确保类型安全代码永远不会中断.

Type casting: It seems to me that this is the biggest problem. Still, there should be ways around this. Since the rules are fixed in the language, the compiler is able to figure out how the members were reordered, and react accordingly. As mentioned above, it will always be possible to prevent reordering in the cases where you want complete control. Also, requirement 2 ensures that type-safe code will never break.

我认为这样的规则有意义的原因是我发现按结构成员的内容分组比按类型分组更自然.当我有很多内部结构时,编译器选择最佳顺序也比我更容易.最佳布局甚至可能是我无法以类型安全的方式表达的布局.另一方面,它似乎使语言更复杂,这当然是一个缺点.

The reason I think such a rule could make sense is because I find it more natural to group struct members by their contents than by their types. Also it is easier for the compiler to choose the best ordering than it is for me when I have a lot of inner structs. The optimal layout may even be one that I cannot express in a type-safe way. On the other hand, it would appear to make the language more complicated, which is of course a drawback.

请注意,我不是在谈论更改语言 - 只有在它可以(/应该)设计不同的情况下.

Note that I am not talking about changing the language - only if it could(/should) have been designed differently.

我知道我的问题是假设性的,但我认为讨论提供了对机器和语言设计的较低层次的更深入的见解.

I know my question is hypothetical, but I think the discussion provides deeper insight in the lower levels of the machine and language design.

我是新来的,所以我不知道我是否应该为此提出一个新问题.请告诉我是否是这种情况.

I'm quite new here, so I don't know if I should spawn a new question for this. Please tell me if this is the case.

推荐答案

C 编译器无法自动对字段重新排序的原因有多种:

There are multiple reasons why the C compiler cannot automatically reorder the fields:

  • C 编译器不知道 struct 是否表示超出当前编译单元的对象的内存结构(例如:外部库、磁盘上的文件、网络数据、CPU 页表,...).在这种情况下,数据的二进制结构也定义在编译器无法访问的位置,因此重新排序 struct 字段将创建与其他定义不一致的数据类型.例如,ZIP 文件中的文件头 包含多个未对齐的32 位字段.重新排序字段将使 C 代码无法直接读取或写入标头(假设 ZIP 实现想要直接访问数据):

  • The C compiler doesn't know whether the struct represents the memory structure of objects beyond the current compilation unit (for example: a foreign library, a file on disc, network data, CPU page tables, ...). In such a case the binary structure of data is also defined in a place inaccessible to the compiler, so reordering the struct fields would create a data type that is inconsistent with the other definitions. For example, the header of a file in a ZIP file contains multiple misaligned 32-bit fields. Reordering the fields would make it impossible for C code to directly read or write the header (assuming the ZIP implementation would like to access the data directly):

struct __attribute__((__packed__)) LocalFileHeader {
    uint32_t signature;
    uint16_t minVersion, flag, method, modTime, modDate;
    uint32_t crc32, compressedSize, uncompressedSize;
    uint16_t nameLength, extraLength;
};

packed 属性阻止编译器根据字段的自然对齐方式对其进行对齐,并且与字段排序问题无关.可以对 LocalFileHeader 的字段重新排序,以便结构既具有最小的尺寸,又使所有字段与其自然对齐对齐.但是,编译器无法选择对字段重新排序,因为它不知道该结构实际​​上是由 ZIP 文件规范定义的.

The packed attribute prevents the compiler from aligning the fields according to their natural alignment, and it has no relation to the problem of field ordering. It would be possible to reorder the fields of LocalFileHeader so that the structure has both minimal size and has all fields aligned to their natural alignment. However, the compiler cannot choose to reorder the fields because it does not know that the struct is actually defined by the ZIP file specification.

C 是一种不安全的语言.C 编译器不知道是否将通过与编译器看到的类型不同的类型访问数据,例如:

C is an unsafe language. The C compiler doesn't know whether the data will be accessed via a different type than the one seen by the compiler, for example:

struct S {
    char a;
    int b;
    char c;
};

struct S_head {
    char a;
};

struct S_ext {
    char a;
    int b;
    char c;
    int d;
    char e;
};

struct S s;
struct S_head *head = (struct S_head*)&s;
fn1(head);

struct S_ext ext;
struct S *sp = (struct S*)&ext;
fn2(sp);

这是一种广泛使用的低级编程模式,尤其是当标头包含位于标头之外的数据的类型 ID 时.

This is a widely used low-level programming pattern, especially if the header contains the type ID of data located just beyond the header.

如果一个 struct 类型嵌入到另一个 struct 类型中,则不可能内联内部的 struct:

If a struct type is embedded in another struct type, it is impossible to inline the inner struct:

struct S {
    char a;
    int b;
    char c, d, e;
};

struct T {
    char a;
    struct S s; // Cannot inline S into T, 's' has to be compact in memory
    char b;
};

这也意味着将某些字段从 S 移动到单独的结构会禁用一些优化:

This also means that moving some fields from S to a separate struct disables some optimizations:

// Cannot fully optimize S
struct BC { int b; char c; };
struct S {
    char a;
    struct BC bc;
    char d, e;
};

  • 因为大多数 C 编译器都在优化编译器,所以重新排序结构字段需要实现新的优化.这些优化是否能够做得比程序员能够编写的更好,这是值得怀疑的.与其他编译器任务(例如寄存器分配、函数内联、常量折叠、将 switch 语句转换为二进制搜索等)相比,手动设计数据结构花费的时间要少得多.因此与传统的编译器优化相比,允许编译器优化数据结构似乎没有那么明显.

  • Because most C compilers are optimizing compilers, reordering struct fields would require new optimizations to be implemented. It is questionable whether those optimizations would be able to do better than what programmers are able to write. Designing data structures by hand is much less time consuming than other compiler tasks such as register allocation, function inlining, constant folding, transformation of a switch statement into binary search, etc. Thus the benefits to be gained by allowing the compiler to optimize data structures appear to be less tangible than traditional compiler optimizations.

    这篇关于为什么 C 编译器不能重新排列结构成员以消除对齐填充?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

  • 查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆