我有以下 struct :

typedef struct Octree {
    uint64_t *data;
    uint8_t alignas(8) alloc;
    uint8_t dataalloc;
    uint16_t size, datasize, node0;
    // Node8 is a union type with of size 16 omitted for brevity
    Node8 alignas(16) node[]; 
} Octree;

为了使在该 struct 上操作的代码按预期工作,需要node0 immediately precedes第一个node,使得((uint16_t *)Octree.node)[-1]将访问Octree.node0.每个Node8实质上是一个union,容纳8个uint16_t.有了GCC,我可以用#pragma pack(push)#pragma pack(pop)强行打包 struct .但是,这是不可移植的.另一种 Select 是:

  • 带上sizeof(uint64_t *) <= sizeof(uint64_t)个人
  • 将 struct 存储为紧跟node数据的2 uint64_t,通过按位运算和指针强制转换手动访问成员

这种 Select 是非常不切实际的.否则我如何以可移植的方式定义这个'packed'数据 struct 呢?还有别的办法吗?

推荐答案

C语言标准不允许您指定struct英寸S的内存布局,直到最后一位.其他语言可以(我会想到Ada和Erlang),但C语言不会.

因此,如果您想要实际的可移植标准C,您可以为您的数据指定一个Cstruct,并使用指针将其转换为特定的内存布局,可能是由许多uint8_t值组成并分解成许多值,以避免字符顺序问题.编写这样的代码很容易出错,需要重复内存,并且根据您的用例,它在内存和处理方面可能都相对昂贵.

如果您想通过C中的struct直接访问内存布局,则需要依赖C语言规范中没有的编译器功能,因此不是"可移植的C语言".

因此,下一个最好的办法是使C代码尽可能地可移植:您可以定义struct,并 for each 支持的平台和编译器组合提供特定于平台/编译器的代码,并且使用struct的代码在每个平台/编译器上可以是相同的.

现在,您需要确保在内存布局与代码和外部接口所需的不完全相同的情况下,不会意外地为平台/编译器进行编译.

从C11开始,使用static_assertsizeofoffsetof就可以做到这一点.

因此,如果您可以要求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");

使用预处理器宏串行化struct名称、成员名称以及可能的大小/偏移量值,可以更简洁地编写包含static_assert个声明的系列,减少错误消息的冗余源代码输入.

正如问题的注释中所指出的,您将需要单独验证未定义的行为&(((uint16_t *)octree.node)[-1]) == &octree.node0实际上是您在此编译器/平台上期望的行为.理想情况下,您会找到一种方法将其作为单独的static_assert声明来编写.但是,像测试这样的测试足够快,以至于您可以将这样的判断添加到运行时代码中的一个很少但保证会运行的函数中,比如全局初始化函数.

C++相关问答推荐

为什么macOS上的FIFA管道比匿名管道慢8倍?

由Go调用E.C.引起的内存快速增长

从组播组地址了解收到的数据包长度

将fget()与strcMP()一起使用不是正确的比较

有没有可能我不能打印?(C,流程)

在C++中头文件中声明外部 struct

向上强制转换C中的数值类型总是可逆的吗?

在CLANG中调试预处理器宏

是什么让numpy.sum比优化的(自动矢量化的)C循环更快?

C中的FREE函数正在触发断点

==284==错误:AddressSaniizer:堆栈缓冲区下溢

使用ld将目标文件链接到C标准库

c程序,让用户输入两类数字,并给出输出用户输入多少个数字

C编译和运行

为什么我在我的代码中得到错误和退出代码-1073741819(0xC0000005),但如果我添加了一个不相关的打印语句,它仍然有效?

意外的C并集结果

使用mmap为N整数分配内存

RISC-V GCC编译器错误编译ASM代码

UEFI 应用程序中的计时器回调仅在 AMI BIOS 中挂起

为什么 C 字符串并不总是等同于字符数组?