【问题标题】:Referencing an aligned empty struct in C?在C中引用对齐的空结构?
【发布时间】:2020-09-22 19:41:15
【问题描述】:

我在 SameBoy 模拟器 (v0.13) 中遇到了一组奇怪的宏,它们似乎使用空结构来寻址数据。它看起来像这样:

#define GB_PADDING(type, old_usage) type old_usage##__do_not_use

#define GB_SECTION(name, ...)     \
        __attribute__ ((aligned (8))) struct {} name##_section_start;  \
        __VA_ARGS__;   \
        struct {} name##_section_end
#define GB_SECTION_OFFSET(name)   \
        (offsetof(GB_gameboy_t, name##_section_start))
#define GB_SECTION_SIZE(name)     \ 
        (offsetof(GB_gameboy_t, name##_section_end) - offsetof(GB_gameboy_t, name##_section_start))
#define GB_GET_SECTION(gb, name)  \
        ((void*)&((gb)->name##_section_start))

似乎GB_gameboy_t 是某种类型(可能是GameBoy 的内部结构)。然而,困扰我的部分是 GB_SECTIONGB_GET_SECTION 宏。很明显,这些宏的目的是对齐数据。但是,我对空结构(标记为name##_section_start)的扩展感到迷茫。 它是否扩展为空(即 0 字节)?如果是这样,那么GB_GET_SECTION 将指向__VA_ARGS__ 是什么。但是,__attribute__ ((aligned (8))) 限定符的意义何在?或者空结构是否扩展为一些垃圾填充字节?如果是,那么GB_GET_SECTION 将指向垃圾数据。

那么它是哪一个?

【问题讨论】:

  • 标准 C 不允许空结构。因此,代码可能使用了某种编译器扩展(GCC 可能确实允许空结构)。
  • @NateEldredge (aligned(8)) 不会让事情复杂化吗?
  • 顺便说一句,“取消引用”结构的意思还不是很清楚;解引用是你对指针所做的事情,结构不是指针。
  • @nanoman 获取地址是引用(与取消引用相反)

标签: c gcc struct padding memory-alignment


【解决方案1】:

标准 C 不允许使用空结构,但 gcc 提供了 extension。它们正是它们看起来的样子,一个大小为 0 的对象,它们完全按照你的期望做,这基本上什么都不是。他们没有可访问的成员。您可以将一个分配给另一个,但它是无操作的。在本例中,它们作为占位符最有用。

__attribute__((aligned (8))) 与通常所做的相同:保证具有此属性的对象在 8 字节边界上对齐。换句话说,它的地址将是 8 的倍数。

在这个程序中,宏用于将大型结构的成员划分为“部分”,每个部分以 8 字节的边界开始,并创建零字节的空结构成员来标记开始和结束每个部分的。代码如下所示:

struct GB_gameboy_s {
    GB_SECTION(foo, int a; short b;);
    GB_SECTION(bar, char c; char d;);
};

typedef struct GB_gameboy_s GB_gameboy_t;

扩展为

struct GB_gameboy_s {
    __attribute__ ((aligned (8))) struct {} foo_section_start;
    int a;
    short b;
    struct {} foo_section_end;
    __attribute__ ((aligned (8))) struct {} bar_section_start;
    short c;
    char d;
    struct {} bar_section_end;
};

所以结构的布局类似于:

  • foo_section_start: 偏移量 0,大小 0
  • a: 偏移量 0,大小 4
  • b: 偏移量 4,大小 2
  • foo_section_end: 偏移量 6,大小 0
  • bar_section_start: 偏移量 8,大小 0
  • c: 偏移量 8,大小 2
  • d:偏移量 10,大小 1
  • bar_section_end: 偏移量 11,大小 0

注意aligned 属性确保bar_section_start,因此还有c,被放置在偏移量8,而不是偏移量6,否则它们可能是。结构的第 7 和第 8 字节有填充,但请注意,此填充位于 before bar_section_start,因为它必须这样才能使对齐有意义。 bar_section_start 指向 填充之后的第一个字节,而不是填充本身。

现在,可以使用offsetof 找到这些成员的偏移量,并使用它来计算每个部分的大小,就像GB_SECTION_SIZE 一样。例如,here 你可以看到他们将各种成员集写入文件,以保存部分虚拟机状态,使用类似

的代码
fwrite(GB_GET_SECTION(bar), GB_SECTION_SIZE(bar), 1, fd)

这具有写入结构的字节 8 到 10 的效果,即 cd 成员。这比一个一个写出所需的成员要方便一些,尤其是因为在实际代码中不止两个。

不清楚为什么需要对齐,但如果写入转储文件的所有内容都是 8 字节的倍数,可能会更方便。复制对齐的缓冲区也会更有效。

他们可以为start/end 成员使用char 或其他一些标准类型,但结构会变得不必要地大。例如,在这种情况下,a 不能放置在偏移量 0 处,因此将被放置在偏移量 4 处,以便像 int 一样为其提供 4 字节对齐。 b 的偏移量为 8,bar_section_start 的偏移量为 16。这意味着foo 部分使用 16 个字节而不是 8,浪费了一定数量的内存和磁盘空间(尽管确实不太可能非常重要)

【讨论】:

  • SameBoy 的作者在这里。对齐是存在的,因此对某个部分的更改不会影响下一个部分。
猜你喜欢
  • 1970-01-01
  • 2013-07-24
  • 2014-02-19
  • 2012-05-02
  • 2016-02-23
  • 2015-08-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多