你对于“为什么不使用大小为1的数组”的直觉是正确的。
这段代码实现了“C结构体黑科技”,但是声明长度为0的数组是一种约束违规。这意味着编译器可以在编译时拒绝你的黑科技,并输出一个停止翻译的诊断消息。
如果我们想要进行黑科技,就必须从编译器中逃脱。
实现“C结构体黑科技”的正确方式(与向后兼容至1989年的ANSI C方言以及可能更早的C方言)是使用一个大小正好为1的完全有效的数组:
struct someData
{
int nData;
unsigned char byData[1];
}
此外,不再使用
sizeof struct someData
来计算
byData
前面部分的大小,而是使用以下代码来计算:
offsetof(struct someData, byData);
为了在
byData
中为42字节的
struct someData
分配空间,我们需要使用以下代码:
struct someData *psd = (struct someData *) malloc(offsetof(struct someData, byData) + 42);
请注意,即使数组大小为零,
offsetof
的计算仍然是正确的。你看,整个结构体的
sizeof
可能包含填充字节。例如,如果我们有这样一个结构体:
struct hack {
unsigned long ul;
char c;
char foo[0];
};
struct hack
的大小可能会因为
ul
成员需要对齐而进行填充。如果
unsigned long
是4字节长的,那么
sizeof(struct hack)
可能是 8,而
offsetof(struct hack, foo)
几乎肯定是5。使用
offsetof
方法是在数组之前获取该结构体前面一部分准确大小的方法。
所以将代码重构为符合经典的、高度可移植的结构体 hack 是最好的做法。
为什么不使用指针呢?因为指针占用额外的空间并且必须被初始化。
还有其他很好的理由不使用指针,即指针需要一个地址空间才能有意义。 结构体 hack 是可外部化的:也就是说,当这样的布局符合外部存储(例如文件、数据包或共享内存)时,不想使用指针,因为它们没有意义。
几年前,我在内核和用户空间之间使用结构体 hack 进行共享内存消息传递接口。我不想在那里使用指针,因为它们只对生成消息的进程的原始地址空间有意义。软件的内核部分使用自己的映射查看该内存,位于不同地址上,因此一切都基于偏移量计算。