【问题标题】:C: Dynamically synthesizing aggregate data types at runtimeC:在运行时动态合成聚合数据类型
【发布时间】:2013-08-16 03:27:15
【问题描述】:

免责声明:这个问题是关于一个一般概念。我已经“简化”了这个问题,以便我可以在这里清楚地问它——无需提供它实际应用的整个上下文。我已经可以预见到诸如“你为什么要那样做?”之类的一堆 cmets。但是,如果这个问题可以从表面上看,我将不胜感激!

假设您想在运行时从一些预定义的结构中动态合成 C 中的一些数据结构。

我知道如何提出这个问题的最佳方法是通过代码示例。

在下文中,我们定义了两个结构:FooBar。我还定义了一个结构FooBar 来说明编译时生成的复合类型和运行时生成的“动态合成”类型之间的至少一个区别。

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

typedef struct Foo {
    char junk1;
    char junk2;
} Foo;

typedef struct Bar {
    int junk3;
    int junk4;
} Bar;

typedef struct FooBar {
    Foo foo;
    Bar bar;
} FooBar;

int main()
{
    printf("Sizes: %li, %li, %li\n", sizeof(Foo), sizeof(Bar), sizeof(FooBar));
    // Prints: Sizes: 2, 8, 12
    // Because Foo is aligned on 1-byte boundaries and has total size of 2 bytes.
    // Bar is aligned on 4-byte boundaries and has total size of 8 bytes.
    // But FooBar is aligned on 4-byte boundaries due to the ints in Foo. Therefore,
    // the compiler added 2-bytes of padding after the foo member.

    // The following "works", but only allocates 10 bytes, and
    // "Bar" members are now "misaligned":
    void * syntheticFooBar = malloc(sizeof(Foo) + sizeof(Bar));
    ((Foo*)syntheticFooBar)->junk1 = 1;
    ((Foo*)syntheticFooBar)->junk2 = 2;
    ((Bar*)(syntheticFooBar + sizeof(Foo)))->junk3 = 3;
    ((Bar*)(syntheticFooBar + sizeof(Foo)))->junk4 = 4;

    free(syntheticFooBar);
    return 0;
}

所以我的问题是:

1.) 缺乏正确的数据对齐会对性能造成多大的影响?考虑到访问合成结构的“成员”所涉及的开销,数据对齐是否是一个重要的影响因素?

2.) 考虑到运行时综合的限制,有没有更好的方法来做到这一点?

【问题讨论】:

  • 您始终可以为要执行此操作的结构关联类型标记并将它们相互嵌入。假设 foo 扩展了 bar,那么 struct foo 中就会有一个 struct bar。使用 containerof 宏可以得到这些扩展。
  • 您是否要避免添加填充?
  • @VaughnCato - 不,我并不是要避免添加填充 - 如果有办法以某种方式即时计算和添加填充(如果这很重要,性能方面),我很乐意这样做。
  • 您必须确保每个成员都有正确的对齐方式。在您的示例中,您会看到 Bar 具有 4 字节对齐(例如),因此您将在合成结构中的 FooBar 之间添加两个额外字节。
  • 这完全取决于处理器。 x86 可能没问题(虽然我不记得double 是否是这样,这往往是最受限制的类型),但如果对齐不正确,肯定会有处理器出现内存总线故障.主要事实是这段代码在 C 标准下具有未指定的行为。您永远无法确定将来的某些系统更改不会导致您的代码核心转储。我会尽快摆脱这个想法。

标签: c struct memory-alignment


【解决方案1】:

1.) 缺乏正确的数据对齐会对性能造成多大的影响?鉴于访问“成员”所涉及的开销 合成结构,数据对齐甚至是一个重要的 影响因素?

这完全取决于 CPU 架构和编译器。在某些系统上,您可能只会受到性能损失,但在其他系统上,您可能会崩溃。

2.) 考虑到运行时综合的限制,有没有更好的方法来做到这一点?

这是一个示例,说明如何在运行时创建正确对齐的合成结构:

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

typedef struct {
  char junk1;
  char junk2;
} A;

typedef struct {
  int junk3;
  int junk4;
} B;

typedef struct {
  double junk5;
  char junk6;
} C;

static size_t roundUp(size_t value,size_t factor)
{
  return value+factor-1-((value+factor-1)%factor);
}

#define alignof(type) (sizeof(struct {type a;char b;})-sizeof(type))

int main(int argc,char **argv)
{
  size_t offsets[3];
  size_t pos = 0;

  pos = roundUp(pos,alignof(A));
  offsets[0] = pos;
  pos += sizeof(A);

  pos = roundUp(pos,alignof(B));
  offsets[1] = pos;
  pos += sizeof(B);

  pos = roundUp(pos,alignof(C));
  offsets[2] = pos;
  pos += sizeof(C);

  {
    char *foobar = malloc(pos);
    A *a = (A *)(foobar + offsets[0]);
    B *b = (B *)(foobar + offsets[1]);
    C *c = (C *)(foobar + offsets[2]);
    a->junk1 = 1;
    a->junk2 = 2;
    b->junk3 = 3;
    b->junk4 = 4;
    c->junk5 = 5;
    c->junk6 = 6;
    free(foobar);
  }
  return 0;
}

特定结构的对齐方式是通过创建具有原始结构和额外字符的结构来确定的。如果您要使用这些结构的数组,编译器会自动添加足够的填充以确保保留必要的对齐方式,因此通过测量大小的差异,我们可以得到正确的对齐方式。

使用对齐信息,我们可以创建一个表,其中显示了合成结构的每个成员相对于开头的位置。我们只需要通过将位置向上舍入到最接近的对齐倍数来确保每个成员的位置具有正确的对齐方式。

您应该能够将此推广到任意数量的成员。

请注意,如果您想确定合成结构的总体大小(sizeof() 可能返回的值),以便您可以创建这些合成结构的数组,那么您需要获取组合对齐要求并向上取整该因素的最终位置。组合对齐将是单个对齐的最小公倍数。

【讨论】:

  • 不错。在我将其标记为已接受之前,您是否介意在问题中添加 cmets 中的信息(未对齐的数据会在某些架构上崩溃)?
  • 您确定偏移量的计算没有错误吗?我得到的数字比看起来正确的要大得多。
  • 似乎有些不对劲。如果你重新排序:C、A、B,那么你的大小和编译器不一致。
  • 我认为答案可能有缺陷,因为编译器使用了所有元素的最大填充。
  • @Steve:原则上,最小公倍数是必要的。例如,如果您有两个成员,其中一个成员有 2 个对齐,而另一个有 3 个对齐,那么您将需要 6 个整体对齐。但是,在大多数情况下,对齐将是 2 的幂,在这种情况下,最小公倍数与最大值相同。
猜你喜欢
  • 2017-06-21
  • 2020-11-05
  • 1970-01-01
  • 2021-11-20
  • 2016-06-10
  • 2019-11-12
  • 2020-05-12
  • 2018-03-04
  • 1970-01-01
相关资源
最近更新 更多