【问题标题】:Are there any guarantees about C struct order?C 结构顺序有什么保证吗?
【发布时间】:2012-06-15 19:41:20
【问题描述】:

我已经广泛使用了结构,并且我看到了一些有趣的东西,尤其是 *value 而不是 value->first_value 其中 value 是指向结构的指针,first_value 是第一个成员,*value 安全吗?

另请注意,由于对齐,大小无法保证,基于架构/寄存器大小的 alginment 值是什么?

我们对齐数据/代码以加快执行速度我们可以告诉编译器不要这样做吗?所以也许我们可以保证结构的某些事情,比如它们的大小?

当对结构成员进行指针运算以定位成员偏移量时,我认为你会使用 - if little endian + for big endian,还是仅取决于编译器?

malloc(0) 真正分配了什么?

以下代码用于教育/探索目的,并不意味着具有生产质量。

#include <stdlib.h>
#include <stdio.h>

int main()
{
    printf("sizeof(struct {}) == %lu;\n", sizeof(struct {}));
    printf("sizeof(struct {int a}) == %lu;\n", sizeof(struct {int a;}));
    printf("sizeof(struct {int a; double b;}) == %lu;\n", sizeof(struct {int a; double b;}));
    printf("sizeof(struct {char c; double a; double b;}) == %lu;\n", sizeof(struct {char c; double a; double b;}));

    printf("malloc(0)) returns %p\n", malloc(0));
    printf("malloc(sizeof(struct {})) returns %p\n", malloc(sizeof(struct {})));

    struct {int a; double b;} *test = malloc(sizeof(struct {int a; double b;}));
    test->a = 10;
    test->b = 12.2;
    printf("test->a == %i, *test == %i \n", test->a, *(int *)test);
    printf("test->b == %f, offset of b is %i, *(test - offset_of_b) == %f\n",
        test->b, (int)((void *)test - (void *)&test->b),
        *(double *)((void *)test - ((void *)test - (void *)&test->b))); // find the offset of b, add it to the base,$

    free(test);
    return 0;
}

调用gcc test.c,然后调用./a.out 我明白了:

sizeof(struct {}) == 0;
sizeof(struct {int a}) == 4;
sizeof(struct {int a; double b;}) == 16;
sizeof(struct {char c; double a; double b;}) == 24;
malloc(0)) returns 0x100100080
malloc(sizeof(struct {})) returns 0x100100090
test->a == 10, *test == 10 
test->b == 12.200000, offset of b is -8, *(test - offset_of_b) == 12.200000

更新 这是我的机器:

gcc --version

i686-apple-darwin10-gcc-4.2.1 (GCC) 4.2.1 (Apple Inc. build 5666) (dot 3)
Copyright (C) 2007 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

uname -a

Darwin MacBookPro 10.8.0 Darwin Kernel Version 10.8.0: Tue Jun  7 16:33:36 PDT 2011; root:xnu-1504.15.3~1/RELEASE_I386 i386

【问题讨论】:

  • “还有什么 malloc(0) 分配?” 啊啊啊啊!请一次一个问题。您在这里问的两件事彼此无关,它们应该在不同的问题中。

标签: c pointers struct


【解决方案1】:

从 6.2.5/20:

结构类型描述了一组顺序分配的非空成员对象 (并且,在某些情况下,一个不完整的数组),每个都有一个可选的 指定的名称和可能的不同类型。

回答:

尤其是 *value 而不是 value->first_value,其中 value 是指向结构的指针,first_value 是第一个成员,*value 安全吗?

见 6.7.2.1/15:

15 在结构对象中,非位域成员和位域所在的单元 驻留的地址按照声明的顺序增加。 指向 a 的指针 结构对象,经过适当转换,指向它的初始成员(或者如果该成员是 位域,然后到它所在的单元),反之亦然。可能有未命名的 在结构对象内填充,但不在其开头。1

然而,结构末尾可能有填充字节,也可能是中间成员。

在 C 中,malloc( 0 ) 是实现定义的。 (附带说明一下,这是 C 和 C++ 不同的小事之一。)

[1] 强调我的。

【讨论】:

  • sequentially allocated nonempty set of member objects 所以假设成员的顺序得到保证对我来说是安全的吗?因为集合不是有序的,是吗?
  • @samy.vilar:是的,订单有保证。这里的术语 set 不是在严格的数学意义上使用,而是表示一组类型化的成员。
  • There may however be padding bytes at the end of the structure (but never in-between members). 这是真的吗?为什么我的偏移量是 8 个字节?是因为它的小端吗?那么数据对齐呢,编译器不会正确填充每个成员以加快访问速度吗?
  • 是的,这是为了遵守对齐约束。字节顺序和对齐是两个不同的东西。详情请见Data Structure Alignment
  • 是的,我知道,但 -8 字节偏移量让我感到意外,因为第一个成员是 int,所以我期待 4 或 -4。
【解决方案2】:

我广泛使用了结构体,并且看到了一些有趣的东西,尤其是*value 而不是value-&gt;first_value,其中value 是指向结构体的指针,first_value 是第一个成员,是*value安全吗?

是的,*value 是安全的;它会生成value 指向的结构的副本。但几乎可以保证它与*value-&gt;first_value 的类型不同,因此*value 的结果几乎总是与*value-&gt;first_value 不同。


反例:

struct something { struct something *first_value; ... };
struct something data = { ... };
struct something *value = &data;
value->first_value = value;

在这种相当有限的情况下,您会从*value*value-&gt;first_value 得到相同的结果。在该方案下,类型将是相同的(即使值不同)。一般情况下*value*value-&gt;first_value的类型是不同的。


还要注意,由于对齐,大小不能保证,但对齐总是在寄存器大小上吗?

由于“寄存器大小”不是定义的 C 概念,因此不清楚您在问什么。在没有 pragma(#pragma pack 或类似的)的情况下,结构的元素将在读取(或写入)值时对齐以获得最佳性能。

我们对齐数据/代码以加快执行速度;我们可以告诉编译器不要这样做吗?所以也许我们可以保证结构的某些事情,比如它们的大小?

编译器负责struct 类型的大小和布局。您可以通过精心设计,或许通过#pragma pack 或类似指令来影响。

当人们关心序列化数据时(或者更确切地说,试图避免通过一次处理一个结构元素来序列化数据)时,通常会出现这些问题。一般来说,我认为你最好编写一个函数来进行序列化,从组件中构建它。

当对结构成员进行指针运算以定位成员偏移量时,我认为如果小端是减法,大端是加法,还是仅取决于编译器?

您最好不要对struct 成员进行指针运算。如果必须,请使用&lt;stddef.h&gt; 中的offsetof() 宏来正确处理偏移量(这意味着您没有直接进行指针运算)。无论是大端还是小端,第一个结构元素始终位于最低地址。实际上,字节顺序与结构内不同成员的布局无关。它只影响结构的(基本数据类型)成员中值的字节顺序。

C 标准要求结构的元素按照定义的顺序排列;第一个元素位于最低地址,下一个位于较高地址,依此类推。不允许编译器更改顺序。在结构的第一个元素之前不能有填充。在编译器认为合适的结构的任何元素之后可以有填充,以确保它认为适当的对齐。结构的大小使得您可以分配 (N × size) 适当对齐的字节(例如通过malloc())并将结果视为结构的数组。

【讨论】:

  • 你假设 *value-&gt;first_value 是一个指针,它可能是也可能不是,是的,它会有不同的类型,因此它总是被强制转换为适当的类型,我问这个问题是为了教育目的,它总是了解事物如何在幕后工作的好主意:) ...
  • 如果你有:struct something { struct something *first_value; ... };struct something data = { ... };struct something *value = &amp;data;value-&gt;first_value = value;,那么你会从 *value*value-&gt;first_value 得到相同的结果。在该方案下,类型将是相同的。一般情况下*value*value-&gt;first_value的类型是不同的。
  • +1;对于The compiler is in charge of the size and layout of struct types. You can influence by careful design and perhaps by #pragma pack or similar directives.
  • 顺便说一句,直接回答和 cmets 怎么样? There may however be padding bytes at the end of the structure (but never in-between members).这是真的吗?
  • 没有括号注释的句子是正确的。括号里的评论是假的。见证:struct InteriorPad { char c; double d; }; 在大多数系统上,cd 之间会有 7 个字节的填充。
【解决方案3】:

调用malloc(0) 将返回一个指针,该指针可以安全地传递给free() 至少一次。如果多个malloc(0) 调用返回相同的值,则每个此类调用可能会释放一次。显然,如果它返回NULL,那么它可以无限次地传递给free() 而没有效果。每次调用 malloc(0) 返回非 null 都应该通过调用 free() 来平衡返回值。

【讨论】:

  • 不回答问题。甚至在“malloc(0) 分配什么?”之前。被编辑了,这个答案只提到了调用返回的内容。
  • @cHao:它可能会分配一些东西,也可能不会。如果它返回NULL,它不会分配任何东西,但我认为没有办法确定它是否分配。用返回指针的free() 平衡对malloc(0) 的每次调用将确保malloc(0) 分配的任何内容都将被释放,但如果free() 的实现使得它在给定一些特定的非时不会无害地执行任何操作-null 指针,不代表实际分配​​,malloc(0) 可以在不分配任何内容的情况下返回这样的指针。
  • 看,现在回答了这个问题。至少在编辑删除该部分之前的问题,以及类似的问题都可以回答。 :)
  • 既然他回答了这个问题,我会把它加回来,谢谢,我想它只是取决于操作系统如何处理 0 字节分配,但假设 malloc 是连续分配的,它仍然很好奇,这我们不能它看起来像分配 16 个字节 malloc(0)) returns 0x100100080 malloc(sizeof(struct {})) returns 0x100100090 虽然我们不能确定这里发生了什么。
  • @samy.vilar:我相信sizeof(struct{}) 是任何允许这样的实现中的最小值,不是吗?
【解决方案4】:

如果您有一个内部结构,则保证从与封闭结构相同的地址开始,如果这是封闭结构的第一个声明。

所以*valuevalue-&gt;first 在下面访问同一地址(但使用不同类型)的内存

struct St {
  long first;
} *value;

另外,结构成员之间的顺序保证与声明顺序相同

要调整对齐方式,您可以使用编译器特定的指令或使用位域。

结构成员的对齐通常基于访问目标平台上各个成员的最佳方式

另外,对于malloc,它可能会在返回的地址附近保留一些簿记,因此即使对于零大小的内存,它也可以返回有效地址(只是不要尝试通过返回的地址访问任何内容)

【讨论】:

  • Also, the ordering between memebers of the structure is guaranteed to be the same as the declaration order 这部分是 C 标准吗?
  • 这几乎是必须的。就像 C 一样低级,如果结构可以在你使用的编译器和构建你的操作系统的编译器之间进行不同的解释,那将会造成严重破坏。
  • 很有趣,但我一直认为语言构造的结构与底层硬件架构无​​关,但我想在操作系统和底层编译代码之间移动数据时它们需要一些凝聚力。
  • @samy.vilar - 在 C(和 C++)中,语言结构通常与硬件架构密切相关(至少与创建时最常见的架构相关)
  • @samy.vilar - 正如我所提到的,对齐(因此“填充”)通常是基于硬件的。您是正确的,整个结构不会映射到硬件上的任何特定内容,但它的(最终原语)成员会映射,这会对结构本身产生影响
【解决方案5】:

了解结构大小的工作方式很重要。例如:

struct foo{
  int i;
  char c;
}

struct bar{
  int i;
  int j;
}

struct baz{
  int i;
  char c;
  int j;
}

sizeof(foo) = 8 bytes (32 bit arch)
sizeof(bar) = 8 bytes
sizeof(baz) = 12 bytes

这意味着结构体的大小和偏移量必须遵循两个规则:

1- 结构必须是它的第一个元素的倍数(为什么 foo 是 8 而不是 5 个字节)

2- 结构元素必须以自身的倍数开始。 (在 baz 中,int j 不能从 6 开始,所以字节 6、7 和 8 是浪费的填充

【讨论】:

  • (我意识到这并不能完全回答你的问题,只是我在代码中经常看到的一个错误)
  • 我很确定 C 标准不要求对齐。意思是int j可以从第 6 个字节开始。决定这是否合法的是架构。
  • 哦哦。我的汇编语言课是谎言吗?我现在正在阅读,如果结果为假,将删除
  • Assembly 不是 C。:) 而且 C 本身不受特定 CPU 规则的约束。但事实证明,在许多情况下,x86 也允许未对齐的值……只是它们的性能很差。
  • 我可能会提到它“通常”以这种方式完成(确实如此)。这不是一定那样,但它很常见,值得一提。
猜你喜欢
  • 1970-01-01
  • 2020-11-14
  • 1970-01-01
  • 2023-03-18
  • 2018-05-19
  • 1970-01-01
  • 2016-11-24
  • 1970-01-01
  • 2012-07-20
相关资源
最近更新 更多