C 语言标准不允许您指定struct
的内存布局一直到最后一位。其他语言可以(例如 Ada 和 Erlang),但 C 不行。
因此,如果您想要实际的便携式标准 C,您可以指定 Cstruct
为您的数据,并使用指针从特定的内存布局转换为特定的内存布局,可能由许多组成和分解为uint8_t
值以避免字节顺序问题。编写此类代码很容易出错,需要复制内存,并且根据您的用例,它在内存和处理方面可能相对昂贵。
如果您想通过struct
在 C 中,您需要依赖 C 语言规范中没有的编译器功能,因此不是“可移植 C”。
因此,下一个最好的办法是使 C 代码尽可能可移植,同时防止为不兼容的平台编译该代码。您定义struct
并为每个受支持的平台和编译器组合提供特定于平台/编译器的代码,以及使用struct
在每个平台/编译器上都可以是相同的。
现在,您需要确保不可能意外地针对内存布局不完全是您的代码和外部接口所需的平台/编译器进行编译。
从 C11 开始,可以使用static_assert
, sizeof
and offsetof
.
因此,如果您可以要求 C11,类似以下的内容应该可以完成工作(我认为您可以要求 C11,因为您正在使用alignas
它不是 C99 的一部分,而是 C11 的一部分)。这里的“可移植 C”部分是修复每个平台/编译器的代码,其中编译由于以下原因之一而失败static_assert
声明失败。
#include <assert.h>
#include <stdalign.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
typedef uint16_t Node8[8];
typedef struct Octree {
uint64_t *data;
uint8_t alignas(8) alloc;
uint8_t dataalloc;
uint16_t size, datasize, node0;
Node8 alignas(16) node[];
} Octree;
static_assert(0x10 == sizeof(Octree), "Octree size error");
static_assert(0x00 == offsetof(Octree, data), "Octree data position error");
static_assert(0x08 == offsetof(Octree, alloc), "Octree alloc position error");
static_assert(0x09 == offsetof(Octree, dataalloc), "Octree dataalloc position error");
static_assert(0x0a == offsetof(Octree, size), "Octree size position error");
static_assert(0x0c == offsetof(Octree, datasize), "Octree datasize position error");
static_assert(0x0e == offsetof(Octree, node0), "Octree node0 position error");
static_assert(0x10 == offsetof(Octree, node), "Octree node[] position error");
该系列的static_assert
使用字符串化的预处理器宏,可以更简洁地编写声明,减少错误消息的冗余源代码类型struct
名称、成员名称,可能还有大小/偏移值。
现在我们已经确定了结构体成员的大小和结构体中的偏移量,但仍有两个方面需要检查。
-
您的代码期望的整数字节顺序与您的内存结构包含的字节顺序相同。如果字节顺序恰好是“本机”,则您无需检查或处理转换。如果字节序是“大字节序”或“小字节序”,则需要添加一些检查和/或进行转换。
-
正如问题评论中所述,您需要单独验证未定义的行为&(((uint16_t *)octree.node)[-1]) == &octree.node0
实际上就是您期望它在这个编译器/平台上的样子。
理想情况下,您会找到一种方法将其写为单独的static_assert
宣言。但是,这样的测试足够快且短,您可以在很少但保证运行的函数(例如全局初始化函数、库初始化函数,甚至构造函数)中向运行时代码添加这样的检查。如果您使用的话请务必小心assert()
该检查的宏,因为如果运行时检查将变成无操作NDEBUG
宏已定义。