7

我正在使用结构并且对它们有几个问题。据我了解,结构变量将按顺序放置在内存中。块(字)的长度取决于机器架构(32 位 - 4 字节,64 位 - 8 字节)。

假设我们有 2 个数据结构:

struct ST1 {
    char c1;
    short s;
    char c2;
    double d;
    int i;
};

在内存中它将是:

32 bit - 20 bytes    
 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
------------------------------------------------------------------------------------------
 c1| PB| s | s | c1| PB| PB| PB| d | d | d  | d  | d  | d  | d  | d  | i  | i  | i  | i  |

64 bit - 24 bytes    | 20 | 21 | 22 | 23 |
previous sequence +  ---------------------
                     | PB | PB | PB | PB |

但是我们可以重新排列它,使这些数据适合机器字。像这样:

struct ST2 {
    double d;
    int i;
    short s;
    char c1;
    char c2;
};

在这种情况下,对于 32 位和 64 位,它将以相同的方式表示(16 个字节):

 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
----------------------------------------------------------------------
 d | d | d | d | d | d | d | d | i | i | i  | i  | s  | s  | ch1| ch2|

我有一些问题:

  • 这就像疯狂的猜测,但主要规则struct是在开始时定义更大尺寸的变量?
  • 据我了解,它不适用于独立变量。喜欢char str[] = "Hello";
  • 填充字节,它有什么代码?它在 ASCII 表的某个地方吗?抱歉,没找到。
  • 2个结构,所有成员都由不同的地址表示在内存中,它们可以不按顺序放置在内存中吗?
  • 这样的结构:struct ST3 { char c1; char c2; char c3;} st3; Has size = 3,我知道如果我们将一个其他类型的成员添加到其中,它将被对齐。但是为什么它之前没有对齐呢?
4

5 回答 5

4

基本规则很简单:

  • 成员必须按顺序排列(除非在 C++ 中您使用 private: public: ... 部分)
  • 成员之间和最后一个之后允许填充

就是这样。剩下的留给实现:类型占用的存储空间,填充量。通常,您可以期望它在ABI中或直接在编译器中正确记录,甚至具有用于操作的工具。

实际上,在某些架构上填充是必要的,例如 SPARC 需要在可被 4 整除的地址上对齐32 位“整数”。在其他架构上,这不是必需的,但未对齐的实体可能需要更多时间来处理,例如80286处理器需要额外的周期来处理从奇数地址读取 16 位实体。(在我忘记之前:类型本身的表示是不同的!)

通常对齐要求或最佳性能完全匹配:您应在与大小相同的边界上对齐。一个很好的反例是80 位浮点数(在某些编译器中可用作 double 或 long double),它喜欢 8 或 16 字节对齐而不是 10。

摆弄填充编译器通常会给你一个设置默认值的开关。这会随着版本的变化而变化,因此最好在升级时加以考虑。_attribute__(packed)以及在gcc中的代码覆盖工具以及MS和许多其他#pragma中的打包工具。这些显然都是对标准的扩展。

底线是,如果你想摆弄布局,你就开始阅读你现在和将来所针对的所有编译器的 dox,以了解它们做什么以及如何控制它。可能还阅读目标平台的 dox,这取决于您首先对布局感兴趣的原因。

一个常见的动机是在您将原始内存写入文件并期望将其读回时具有稳定的布局。也许在不同的平台上使用不同的编译器。在新的平台类型进入现场之前,这是更容易的。

其他动机是表现。那个更棘手,因为规则变化很快,而且效果很难马上预测。在英特尔上说,基本的“未对齐”惩罚已经消失了很长时间,而重要的是在高速缓存行内。缓存行大小因处理器而异。同样使用更多的填充可能会产生更好的个体,而完全打包的结构在缓存使用方面更经济。

有些操作需要适当的对齐,但不是由编译器直接强制执行,您可能需要应用特殊的对齐编译指示(例如某些与SSE相关的东西)。

底线重复:停止猜测,确定你的目标并阅读正确的dox。(顺便说一句,阅读SPARCIA32和其他的架构手册对我来说非常有趣,并且在很多方面都有收获。)

于 2013-06-07T11:12:24.970 回答
0

按提出的方式回答您的问题(忽略您对结构的非常漂亮的图片)

这就像疯狂的猜测,但 struct 的主要规则是在开始时定义更大尺寸的变量?

总是把最需要对齐的东西放在第一位。例如,我不会放char[99]第一个。一般来说,这可以作为指针、64 位本机类型、32 位本机类型等,但如果您的结构包含其他结构的成员,则必须非常小心。

据我了解,它不适用于独立变量。喜欢char str[] = "Hello";

我真的不明白这一点。如果您在堆栈上定义一个 char 数组,它具有 char 对齐。如果你定义一个 char 数组后跟一个 int,堆栈上可能会有填充,你只是找不到它。

填充字节,它有什么代码?它在 ASCII 表的某个地方吗?抱歉,没找到。

它既没有代码也没有数据。它是编译器插入的填充,可以包含任何值,在相同或不同的程序运行中,结构的不同实例之间可能会或可能不会不同。

2个结构,所有成员都由不同的地址表示在内存中,它们可以不按顺序放置在内存中吗?

我不明白。您是在问编译器是否可以在结构之间插入填充?如果不是,请澄清,因为这个答案不会有太大帮助;

当编译器创建一个结构时,它必须使您能够合理地创建一个此类结构的数组。考虑一下:

struct  S {
    int wibble;
    char wobble;
};

S stuff[2];

如果编译器在 wobble 之后没有插入 3 个字节的填充,则访问stuff[1].wobble将无法正确对齐,这将导致某些硬件崩溃(以及其他硬件上的糟糕性能)。基本上,编译器必须确保在末尾进行填充,以确保结构中最对齐的成员始终与此类结构的数组正确对齐。

这样的结构:struct ST3 { char c1; char c2; char c3;} st3;size = 3,我知道如果我们将一个其他类型的成员添加到其中,它将被对齐。但是为什么它之前没有对齐呢?

你的意思是“为什么编译器不把它放在正确对齐的地方”?因为语言不允许。不允许编译器重新排序结构的成员。只允许插入填充。

于 2013-06-07T09:37:46.517 回答
0

结构(和类)成员的对齐方式取决于平台,确实如此,但也取决于编译器。将成员与其大小对齐的原因是出于性能原因。使所有整数类型与其大小对齐会减少内存访问。

您通常可以强制编译器减少对齐,但除非出于特定原因(例如,为了不同平台之间的数据兼容性,作为通信数据),这不是一个好主意。在 Visual C++ 中存在#pragma pack为此,例如:

#pragma pack(1)
struct ST1 {
    char c1;
    short s;
    char c2;
    double d;
    int i;
};

assert(sizeof(ST1) == 16);

但正如我之前所说,通常不是一个好主意。

请记住,编译器不仅仅是在某些字段之后添加填充字节。它还确保结构在内存中分配,所有字段都是右对齐的。我的意思是,在您的 ST1 示例中,因为较大的字段类型是双精度,编译器将确保d字段将对齐 8 个字节(除非使用#pragma pack或类似选项):

ST1 st1;

assert(&st1.d % 8 == 0);

关于您的问题:

  • 如果你想节省空间,是的,按大小排序字段是一个很好的技巧,先写越大。在组合结构的情况下,使用内部结构的较大字段的大小,而不是结构的大小。
  • 它正在处理独立变量。但是编译器可以对内存中的变量进行排序(而不是结构和类的成员)。

例如:

short   s[27];
int32_t i32[34];
int64_t i64[45];

assert(s % 2 == 0);
assert(i32 % 4 == 0);
assert(i64 % 8 == 0);
  • 填充字节可以包含任何内容。通常初始化数据(至少你初始化它)。出于调试原因,有时编译器可能包含特定的字节模式。
  • 关于所有成员在内存中由不同地址表示的结构:对不起,我不太明白你在问什么。
  • 标准 C++ 说结构/类的地址必须与此类结构/类的第一个字段的地址相同。然后,只有在 之后才有可能填充c3,但在 之前永远不会c1

从 N3337 (C++11) [9.2 class.menu, p.20]:

指向标准布局结构对象的指针,使用 a 进行适当转换reinterpret_cast,指向其初始成员(或者如果该成员是位字段,则指向它所在的单元),反之亦然。[注意:因此,标准布局结构对象中可能存在未命名的填充,但不是在其开头,这是实现适当对齐所必需的。——尾注]

于 2013-06-07T09:51:50.047 回答
0

对于英特尔架构上的 gcc,它需要更多的指令和周期来访问(读/写)奇数内存地址。因此添加填充以达到偶数内存地址

于 2013-11-13T06:55:36.020 回答
-1

请注意,您不确定您的变量是否对齐(但通常是对齐的)。如果你使用 GCC,你可以使用属性packed 来确保你的数据是对齐的。

例子 :

struct foo {
    char c;
    int x;
} __attribute__((packed));

据我了解,它不适用于独立变量。像 char str[] = "Hello";?

该表将在您的记忆中对齐。

于 2013-06-07T08:44:54.733 回答