【问题标题】:Forcing the size of an enum'd field in a structure强制结构中枚举字段的大小
【发布时间】:2017-08-19 18:07:11
【问题描述】:

[注意:这与 Specifying size of enum type in CWhat is the size of an enum in C? 有关,但在这些问题中,问题是如何最小化封闭结构的大小。在这里,我们要指定结构的各个成员的大小,但仍然获得 typedef 枚举的文档优势]

我正在实现串行通信协议的客户端。我想使用 C 结构来捕获串行数据包的格式,我想使用 C 枚举来定义数据包中各个插槽的合法值。

我没有看到两种方法都可以,但由于我使用的是 GCC,所以可能有可能。

作为问题的假设示例,假设我收到的数据包如下所示:

typedef struct {
  uint16_t appliance_type;
  uint8_t voltage;
  uint16_t crc;
} __attribute__((__packed__)) appliance_t;

这很清楚,我很确定我会得到一个 5 个字节长的结构。但它没有捕捉到appliance_typevoltage 只能采用某些值的事实。

我更喜欢这样的……

typedef enum {
  kRefrigerator = 600,
  kToaster = 700,
  kBlender = 800
} appliance_type_t;

typedef enum {
  k120vac = 0,
  k240vac = 1,
  k12vdc = 2
} voltage_t;

typedef struct {
  appliance_type_t appliance_type;
  voltage_t voltage;
  uint16_t crc;
} appliance_t;

...但据我所知,无法指定 Appliance_type_t 为 16 位,而 voltage_t 为 8 位。

有没有办法让我的蛋糕也吃掉?

更新:

我需要明确指出,我不希望编译器将枚举值强制为各个字段的设置器!相反,我发现 typedef 的枚举对于代码的维护者来说是一个有用的构造,因为它明确了预期的值是什么。

注意:当我对此进行研究时,我注意到 GCC 枚举接受 __attribute__ 规范,但我不确定这是否有帮助。

【问题讨论】:

  • 你应该摆脱打包结构并序列化/反序列化你的数据包......
  • 枚举常量的类型是int。您可以将任意整数分配给枚举变量。它没有提供您认为的类型安全。
  • 在 C++ 中可以设置 type of enum members 但我不知道 C 中是否有类似的东西
  • @Lundin - 我的书呆子想争辩说,这是指针类型提供的类型安全,而不是枚举类型的常量:)
  • @StoryTeller 这启发了我想出一个小技巧来解决这个问题,在这里:stackoverflow.com/questions/43043246/…

标签: c gcc struct enums


【解决方案1】:

这样做的一种方法是将位字段与结构中成员的定义一起使用。

所以你的结构是 -

typedef struct {
    appliance_type_t appliance_type:16;
    voltage_t voltage:8;
    uint16_t crc;
} appliance_t;

但这会在电压字段之后留下一个填充(虽然取决于编译器的实现)。打包属性应该可以帮助您。

【讨论】:

  • 这完全是非标准的 C。一般来说,位域是一个非常糟糕的主意,因为它们的指定非常差,不存在可移植性。
  • 无论我们多么讨厌它,我们都被非标准代码所包围。但是,是的,我们不应该为此做出贡献。
【解决方案2】:

我建议您将“原始数据”与抽象分开。例如:

typedef struct {
  uint16_t type;
  uint8_t  voltage;
  uint16_t crc;
} __attribute__((__packed__)) raw_data_t;

typedef struct {
  appliance_type_t type;
  voltage_t voltage;
  uint16_t crc;
} appliance_t;

inline appliance_t raw_data_to_appliance (const raw_data_t* raw)
{
  appliance_t app =
  {
    .type    = (appliance_type_t)raw->type,
    .voltage = (voltage_t)raw->voltage,
    .crc     = raw->crc,
  };
  return app;
}

这不应该产生太多的开销代码。另见How to create type safe enums?

【讨论】:

  • 在此和进行正确序列化之间的步骤并不大:在我看来,使用编译器扩展而不是可移植序列化函数是不值得的。
  • 此外packed structs 的意思是unaligned memory access,应该避免......
  • @LPs “应该避免打包结构”。哦,我同意,但试着告诉我的内存和闪存有限的 ARM 处理器...... :)
  • @fearless_fool 这不一定是您需要做出的权衡。如果成员放置不利于对齐并且结构被大量使用,您最终可能会使用 more 带有打包结构的闪存。
  • 使用序列化/反序列化通常是个好主意。我使用了问题中提供的结构。如果它只是一个原始的uint8_t [],那将是一个不同的故事。 (但是你会遇到“严格混叠”的麻烦。)
【解决方案3】:

所以我知道当你声明一个枚举时,你无法控制它的大小和 in

typedef struct {
  appliance_type_t appliance_type;
  voltage_t voltage;
  uint16_t crc;
} appliance_t;

你想要例如voltage_t voltage 具有特定的大小,比如说uint8_t,但您无法指定。

我不知道您是否可以对枚举提供大小限制,但您可以使用两种机制。在这两种机制中,您都将 unit8_t voltage 指定为结构的成员:

typedef struct {
  uint8_t appliance_type;
  uint8_t voltage;
  uint16_t crc;
} appliance_t;

方法 1 使用普通的旧 #defines。您只需#define 值并在赋值中使用符号常量:

#define k120vac 0
appliance.voltage= k120vac;

在方法 2 中,您有枚举,但仍然声明了您想要的大小的成员。现在您可以将枚举值分配给成员,例如:

appliance.voltage= k120vac;


注意:我运行了我的简单测试(VC2008)并且还使用voltage_t voltage; 声明,您可以执行appliance.voltage= kToaster; 所以甚至没有给编译器“枚举类型安全”(仅检查域中的值被分配/使用),所以不即使是我展示的两种方法也丢失了。
又做了一些测试:
unsigned char x = k12vdc;    // <== OK
unsigned char y = kToaster;  // <== warning: truncation of int to unsigned char

所以编译器在赋值时似乎使用了枚举值的最小类型

似乎没有与枚举本身相关的大小,只有它的各个值。重新定义 k12vdc= 2000 现在还会对分配 x= k12vdc 发出警告,但不会对 x=k120vac 发出警告。

现在,鉴于编译器 (VC2008) 不强制枚举类型安全,并且您无法控制枚举类型的变量或成员的大小,声明变量或成员似乎没有用枚举类型,但可能出于文档目的。枚举类型的变量/成员将是 int 的大小,如下例所示:

appliance_type_t a;
voltage_t v;
printf("sizeof appliance_type_t= %d\n", sizeof(a));  // prints '4'
printf("sizeof voltage_t= %d\n", sizeof(v));         // prints '4'

【讨论】:

  • 这如何回答问题或提供 OP 不知道的信息? OP 要求提供确切的成员大小 类型安全性。两者都没有给你后者。
  • @user694733,正如我在 VCV2008 上的测试所示,都不可能。对于枚举,您无法控制其大小,并且编译器不会防止错误的分配。所以也许我的回答应该是“不,不可能”?还是可以在另一个编译器上使用?
  • @PaulOgilvie:对不起,如果我误导了你——我已经更新了 OP 以明确表示我不是在寻找你所说的“类型安全”。请参阅那里的“更新”...
  • 无畏的傻瓜,我又做了一些测试。在我更新的答案中查看结果:枚举本身似乎没有大小,只有它的各个值。
猜你喜欢
  • 2016-01-16
  • 1970-01-01
  • 2014-08-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-02-07
相关资源
最近更新 更多