您对“为什么不使用大小为 1 的数组”的直觉是正确的。
代码做错了“C struct hack”,因为零长度数组的声明是违反约束的。这意味着编译器可以在编译时立即拒绝您的 hack,并带有停止翻译的诊断消息。
如果我们想进行黑客攻击,我们必须偷偷溜过编译器。
进行“C struct hack”(与可追溯到 1989 年 ANSI C 并且可能更早的 C 方言兼容)的正确方法是使用大小为 1 的完全有效的数组:
struct someData
{
int nData;
unsigned char byData[1];
}
此外,sizeof struct someData
之前零件的尺寸不是 ,而是byData
使用以下公式计算的:
offsetof(struct someData, byData);
要在 中分配struct someData
42 个字节的空间byData
,我们将使用:
struct someData *psd = (struct someData *) malloc(offsetof(struct someData, byData) + 42);
请注意,offsetof
即使在数组大小为零的情况下,此计算实际上也是正确的计算。你看,sizeof
整个结构可以包括填充。例如,如果我们有这样的事情:
struct hack {
unsigned long ul;
char c;
char foo[0]; /* assuming our compiler accepts this nonsense */
};
struct hack
由于成员的原因,很可能会填充的大小以进行对齐ul
。如果unsigned long
是四个字节宽,那么很可能sizeof (struct hack)
是 8,而offsetof(struct hack, foo)
几乎可以肯定是 5。该offsetof
方法是获取数组之前结构前一部分的准确大小的方法。
所以这将是重构代码的方法:使其符合经典的、高度可移植的 struct hack。
为什么不使用指针?因为指针占用了额外的空间并且必须被初始化。
不使用指针还有其他充分的理由,即指针需要地址空间才能有意义。struct hack 是可外部化的:也就是说,在某些情况下,这种布局符合外部存储,例如文件、数据包或共享内存的区域,在这种情况下,您不需要指针,因为它们没有意义。
几年前,我在内核和用户空间之间的共享内存消息传递接口中使用了 struct hack。我不想要那里的指针,因为它们只对生成消息的进程的原始地址空间有意义。该软件的内核部分使用自己在不同地址的映射来查看内存,因此一切都基于偏移计算。