c - 在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 提供的扩展。它们正是它们看起来的样子,一个大小为 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,大小 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
,被放置在 offset 8
,而不是在 offset 6
,因为它们可能是。结构的第 7 和 8 字节有填充,但请注意,此填充位于之前 bar_section_start
,因为它必须这样才能使对齐有意义。 bar_section_start
指向填充后的第一个字节,而不是填充本身。
现在,可以使用offsetof
来找到这些成员的偏移量,并使用它来计算每个部分的大小,就像GB_SECTION_SIZE
这样做一样。例如,在这里您可以看到他们将各种成员集写入文件,以保存部分虚拟机状态,使用如下代码
fwrite(GB_GET_SECTION(bar), GB_SECTION_SIZE(bar), 1, fd)
这具有写入结构的字节 8 到 10 的效果,即c
andd
成员。这比一个一个地写出所需的成员要方便一些,尤其是因为在实际代码中不止两个。
目前尚不清楚为什么需要对齐,但如果写入转储文件的所有内容都是 8 字节的倍数,可能会更方便。复制对齐的缓冲区也可能更有效。
他们可以char
为成员使用或其他一些标准类型start/end
,但结构会变得不必要地大。例如,在这种情况下,a
不能将其放置在偏移量 0 处,因此将放置在偏移量 4 处,以便像往常一样为其提供 4 字节对齐int
。 b
将在偏移量 8 处,并且bar_section_start
将在偏移量 16 处。这将意味着该foo
部分使用 16 个字节而不是 8 个字节,浪费了一定数量的内存和磁盘空间(尽管它确实不太可能非常重要)
推荐阅读
- c# - 数据库操作预计会影响 1 行,但实际上会影响 0 行实体框架核心
- c# - 在此示例中,如何将具有泛型类型的从同一接口继承的不同对象存储在单个变量中?
- java - Tomcat 无法识别 Linux 中的上下文文件
- swift - Swift 从一个圆的中心到另一个圆的边缘画一条线
- python-3.x - H2OTypeError:“training_frame”必须是有效的 H2OFrame
- python - 带有或逻辑运算符的布尔索引numpy数组
- javascript - 如何用玩笑的查询来模拟函数
- javascript - 如何逐步将带有(this)的按钮传递给3个功能
- pandas - 如何从 jupyter 笔记本创建业务就绪报告?
- sql - DB2 i 7.3:编写循环(使用日期变量)从选择中插入值的存储过程