强制结构中枚举字段的大小

时间:2017-03-27 06:44:52

标签: c gcc struct enums

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

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

我没有办法做到这两点,但因为我正在使用GCC,所以有可能。

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

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

这很清楚,我很确定我会得到一个长度为五个字节的结构。但它没有捕捉到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位。

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

更新

我需要明确表示我并不期望编译器将enum'd值强制为各个字段的setter!相反,我发现typedef'd枚举是代码维护者的有用构造,因为它明确了预期的值。

注意:在我研究过这个问题时,我注意到GCC枚举会接受__attribute__规范,但我不确定是否有帮助。

3 个答案:

答案 0 :(得分:0)

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

所以你的结构将是 -

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

但这会在电压场之后留下填充(虽然取决于编译器的实现)。 packed属性可以帮助你。

答案 1 :(得分:0)

我建议您保留"原始数据"与抽象分开。例如:

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?

答案 2 :(得分:0)

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

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

你想要的,例如voltage_t voltage具有特定尺寸,请说uint8_t,但您无法指定。{/ p>

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

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

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

#define k120vac 0
appliance.voltage= k120vac;

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

appliance.voltage= k120vac;

<小时/> 注意:我运行了简单测试(VC2008)和voltage_t voltage;声明,你可以执行appliance.voltage= kToaster;所以甚至不给编译器&#34;枚举类型安全&#34; (只检查/使用来自域的值),所以即使用我展示的2种方法也没有丢失。 做了一些测试:

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'