在 C 中引用对齐的空结构?
Referencing an aligned empty struct in C?
我在 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_SECTION
和 GB_GET_SECTION
宏。很明显,这些宏的目的是对齐数据。但是,我不知道空结构(标记为 name##_section_start
)扩展到什么。 它是否扩展为空(即 0 字节)?如果是这样,那么 GB_GET_SECTION
将指向 __VA_ARGS__
是什么。但是 __attribute__ ((aligned (8)))
限定词的意义何在?或者 空结构是否扩展为一些垃圾填充字节?如果是,那么 GB_GET_SECTION
将指向垃圾数据。
那么是哪一个呢?
标准 C 不允许空结构,但 gcc 提供 extension。它们就是它们看起来的样子,一个大小为 0 的对象,并且它们的作用完全符合您的预期,这实际上什么也没有。他们没有可以访问的成员。您可以将一个分配给另一个,但这是一个空操作。它们最适合用作占位符,如本例所示。
__attribute__((aligned (8)))
做它通常做的事情:保证具有此属性的对象在 8 字节边界上对齐。换句话说,它的地址将是8的倍数。
在这个程序中,宏被用来将一个大结构的成员分成"sections",每个都从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 中有填充,但请注意,此填充出现在 之前 bar_section_start
,因为它必须是为了使对齐有意义。 bar_section_start
指向第一个字节 在 填充之后,而不是填充本身。
现在,可以使用 offsetof
找到这些成员的偏移量,并使用它来计算每个部分的大小,就像 GB_SECTION_SIZE
所做的那样。例如,here 你可以看到他们将各种成员集写入一个文件,以保存部分虚拟机状态,使用类似
的代码
fwrite(GB_GET_SECTION(bar), GB_SECTION_SIZE(bar), 1, fd)
这具有写入结构的字节 8 到 10 的效果,即 c
和 d
成员。这比一个一个地写出所需的成员要方便一点,尤其是在实际代码中有两个以上的成员。
目前还不清楚为什么需要对齐,但如果写入转储文件的所有内容都是 8 字节的倍数,可能会更方便。复制对齐的缓冲区也会更有效。
他们可以为 start/end
成员使用 char
或其他一些标准类型,但结构会变得不必要地大。例如,在那种情况下,a
不能放在偏移量 0 处,因此会放在偏移量 4 处,以便像 int
一样为其提供 4 字节对齐。 b
将位于偏移量 8,而 bar_section_start
将位于偏移量 16。这意味着 foo
部分使用 16 个字节而不是 8 个字节,从而浪费一定数量的内存和磁盘space(虽然确实不太可能非常重要)
我在 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_SECTION
和 GB_GET_SECTION
宏。很明显,这些宏的目的是对齐数据。但是,我不知道空结构(标记为 name##_section_start
)扩展到什么。 它是否扩展为空(即 0 字节)?如果是这样,那么 GB_GET_SECTION
将指向 __VA_ARGS__
是什么。但是 __attribute__ ((aligned (8)))
限定词的意义何在?或者 空结构是否扩展为一些垃圾填充字节?如果是,那么 GB_GET_SECTION
将指向垃圾数据。
那么是哪一个呢?
标准 C 不允许空结构,但 gcc 提供 extension。它们就是它们看起来的样子,一个大小为 0 的对象,并且它们的作用完全符合您的预期,这实际上什么也没有。他们没有可以访问的成员。您可以将一个分配给另一个,但这是一个空操作。它们最适合用作占位符,如本例所示。
__attribute__((aligned (8)))
做它通常做的事情:保证具有此属性的对象在 8 字节边界上对齐。换句话说,它的地址将是8的倍数。
在这个程序中,宏被用来将一个大结构的成员分成"sections",每个都从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,大小 0a
:偏移量 0,大小 4b
:偏移量 4,大小 2foo_section_end
:偏移量 6,大小 0bar_section_start
:偏移量 8,大小 0c
:偏移量 8,大小 2d
:偏移量 10,大小 1bar_section_end
:偏移量 11,大小 0
请注意,aligned
属性已确保 bar_section_start
以及 c
位于偏移量 8
,而不是偏移量 6
否则可能会这样。在结构的字节 7 和 8 中有填充,但请注意,此填充出现在 之前 bar_section_start
,因为它必须是为了使对齐有意义。 bar_section_start
指向第一个字节 在 填充之后,而不是填充本身。
现在,可以使用 offsetof
找到这些成员的偏移量,并使用它来计算每个部分的大小,就像 GB_SECTION_SIZE
所做的那样。例如,here 你可以看到他们将各种成员集写入一个文件,以保存部分虚拟机状态,使用类似
fwrite(GB_GET_SECTION(bar), GB_SECTION_SIZE(bar), 1, fd)
这具有写入结构的字节 8 到 10 的效果,即 c
和 d
成员。这比一个一个地写出所需的成员要方便一点,尤其是在实际代码中有两个以上的成员。
目前还不清楚为什么需要对齐,但如果写入转储文件的所有内容都是 8 字节的倍数,可能会更方便。复制对齐的缓冲区也会更有效。
他们可以为 start/end
成员使用 char
或其他一些标准类型,但结构会变得不必要地大。例如,在那种情况下,a
不能放在偏移量 0 处,因此会放在偏移量 4 处,以便像 int
一样为其提供 4 字节对齐。 b
将位于偏移量 8,而 bar_section_start
将位于偏移量 16。这意味着 foo
部分使用 16 个字节而不是 8 个字节,从而浪费一定数量的内存和磁盘space(虽然确实不太可能非常重要)