可以使用 C 中的宏从各种结构构建预编译时间的任意字节数组吗?

Can arbitrary byte arrays be constructed pre-compile time from various structs using macros in C?

我们都知道我们可以 "mash" 在 C 中将字符串文字组合在一起,而大多数编译器不会打扰我们,例如 char[] result = "a" "b"; // result = "ab"。我想将这个想法推广到结构。

假设我有以下结构:

typedef struct s1 {
 char a;
 int b;
} s1_t;

typedef struct s2 {
 int c;
} s2_t;

实际上我想声明的字节数组是 s1_ts2_t 的任意组合。这合理吗?我宁愿不使用工会。结构的内容将在编译时已知。

We all know that we can "mash" together string literals in C without most compilers troubling us

事实上,没有符合标准的 C 编译器会抱怨,因为此类结构的语义由标准明确定义。我提到这个是为了强调结果不是偶然的,取决于运气或编译器的突发奇想,或任何类似的事情,因为你的评论似乎允许这种可能性。

Effectively I want to declare byte arrays that are arbitrary combinations of [two structure types]. Is this reasonably possible? I would prefer not to use unions.

严格来说是不可能的。您不能以任何组合使用 struct 文字来初始化字节数组。您可以在 C99 或更高版本中使用联合实现一些相当接近的东西,如果您希望从 struct 文字进行编译时初始化,那么我看不到任何其他选项。它看起来像这样:

typedef union {
    s1_t s1;
    s2_t s2;
} s_u;

s_u array[] = {
    { .s1 = (s1_t) { 'a', 42 } },
    { .s1 = (s1_t) { 'b', 17 } },
    { .s2 = (s2_t) { 1856 } },
    { .s1 = (s1_t) { 'Q', -1 } }
};

unsigned char *byte_array = (unsigned char *)array;

但是,我想您想要避免并集的原因是将 struct 不同大小的表示打包在一起而不进行填充。这不可能。事实上,即使只有 one 结构类型,也不一定可以在一个实例的最后一个元素和下一个实例的第一个元素之间没有任何填充的情况下安排实例。即使用 memcpy() 强制执行它也可能不容易,因为 struct 的表示可以同时具有尾部填充和内部填充,所有这些都以它们的大小计算。并且不要忘记任何填充字节的值都是未定义的。

您将所追求的结果描述为 "sequential bytecode" 和“序列化 结构”(添加了强调)。我不能确定 "serialized" 在这种情况下对你意味着什么,但对我来说它通常意味着与 "internal representation" 完全不同的东西。事实上,避免对内部表示的依赖是序列化的主要原因之一。

如果您的 objective 符合我的序列化理念,那么避免让您的用户编写字节码的最佳选择是为他们提供一个字节码编译器,以最方便的形式输入和输出。

如果您的数据结构定义在一个单独的文件中,该代码可以 #includeed 多次,而不同的宏已生效。

假设您的数据在文件 foo.dat 中,格式如下:

INTS(1,2)
IDBL(-4,3)
INTS(5,23)

项目应分行列出,没有分号

可以从做以下事情开始:

#define INTS(x,y) INT_PAIR Field ## __LINE__;
#define IDBL(x,y) INT_DBL  Field ## __LINE__;
struct ALL_DATA {
#include "foo.dat"
  int END_OF_DATA;
};
#undef INTS
#undef INTS

然后是:

#define INTS(x,y) {(x),(y)},
#define IDBL(x,y) {(x),(y)},
const struct ALL_DATA all_data = {
#include "foo.dat"
  0};
#undef INTS
#undef INTS

那时会有一个编译时常量结构,其中包含其他结构类型的混合串联,如果它是根据某种形式构造的,则可以在 运行-time 进行解析已知规则(例如,所有 int/int 对的第一个值为正,所有 int/double 对的第一个值为负,数据结尾为零)。

如果想要一个包含所有项目(以 int 大小的单位)从结构开始的起始偏移量的数组,可以使用更多的包含物:

#define INTS(x,y) INT_PAIR FIELD_ID_ ## __LINE__, dummy1x ## __LINE__,
#define IDBL(x,y) INT_DBL  FIELD_ID_ ## __LINE__, \
    dummy1x ## __LINE__, dummy2x ## __LINE__,
enum DATA_IDS {
#include "foo.dat"
  dummy_end_id};
#undef INTS
#undef INTS
#define INTS(x,y) FIELD_ID_ ## __LINE__, 
#define IDBL(x,y) FIELD_ID_ ## __LINE__,
unsigned int data_offsets[] = {
#include "foo.dat"
  -1};
#undef INTS
#undef INTS

如果不滥用 __LINE__ 指令或要求数据文件的每一行都提供唯一的标识符名称,我不知道有什么方法可以使它工作;哪种方法更可取,可以公开讨论。