fearless_fool
fearless_fool

Reputation: 35179

Forcing the size of an enum'd field in a structure

[NOTE: this is related to Specifying size of enum type in C and What is the size of an enum in C?, but in those questions, the issue was how to minimize the size of the enclosing struct. Here, we want to specify the size of the individual members of the struct, but still get the documentation advantage of typedef'd enums]

I'm implementing the client side of a serial communication protocol. I'd like to use C structs to capture the format of a serial packet, and I'd like to use C enums to define the legal values of the various slots in the packet.

I don't see a way to do both, but since I'm using GCC, it may be possible.

As a hypothetical example of the problem, assume the packet I receive looks like this:

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

That's pretty clear, and I'm pretty sure I'll get a struct that is five bytes long. But it doesn't capture the fact that appliance_type and voltage can only take on certain values.

I'd prefer something like this...

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;

...but there's no way that I know of to specify that appliance_type_t is 16 bits and voltage_t is 8 bits.

Is there a way to have my cake and eat it too?

update:

I need to make it clear that I'm NOT expecting the compiler to enforce the enum'd values as setters for the respective fields! Rather, I find that typedef'd enums are a useful construct for maintainers of the code, since it makes explicit what the intended values are.

Note: As I researched this, I noticed that GCC enums accept __attribute__ specifications, but I'm not sure that helps.

Upvotes: 1

Views: 5844

Answers (3)

Lundin
Lundin

Reputation: 213902

I would suggest that you keep the "raw data" separate from the abstraction. For example:

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;
}

This shouldn't yield much overhead code. See also How to create type safe enums?

Upvotes: 0

Paul Ogilvie
Paul Ogilvie

Reputation: 25286

So I understand that when you declare an enum, you have no control over its size and in

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

you want e.g. voltage_t voltage to be of a specific size, let's say uint8_t but you are not able to specify that.

I don't know if you can provide size constraints on an enum, but there are two mechanisms you can use. In both mechanisms you specify unit8_t voltage as the member of the struct:

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

Method 1 uses plain old #defines. You just #define the values and use the symbolic constants in assignments:

#define k120vac 0
appliance.voltage= k120vac;

In method 2 you have the enums, but still declared a member of the size you want. Now you can assign the enum values to the member, e.g.:

appliance.voltage= k120vac;


Note: I ran i simple test (VC2008) and also with voltage_t voltage; declaration, you can do appliance.voltage= kToaster; so not even gives the compiler "enum type safety" (checking only values from the domain are assigned/used), so not even that has been lost with the 2 methods I showed.
Did some more tests:

unsigned char x = k12vdc;    // <== OK
unsigned char y = kToaster;  // <== warning: truncation of int to unsigned char

So it seems the compiler uses the smallest type possible for the values of the enum when assigning.

There seems no size associated with the enum itself, only with its individual values. Redefining k12vdc= 2000 now also gives a warning on the assignment x= k12vdc but not on x=k120vac.

Now, given that the compiler (VC2008) doesn't enforce type safety of enums, and that you can't control the size of a variable or member of an enum type, there seems no use in declaring a variable or member of enum type, except maybe for documentation purposes. A variable/member of enum type will be the size of an int, as the following example shows:

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'

Upvotes: 0

Ajay Brahmakshatriya
Ajay Brahmakshatriya

Reputation: 9203

One way of doing this would be to use the bit field with the definition of the member in the struct.

So your struct would be -

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

But this will leave a padding after the voltage field (depends on the implementation of the compiler though). The packed attribute should help you with that.

Upvotes: 1

Related Questions