peets
peets

Reputation: 441

replacing "__attribute__ ((packed))" for compact structures by a standard (portable) C method

Conversion of several values to a byte-string for radio transmission has to avoid unneeded bytes. Using GCC on an ARM target (32 bit) I use "attribute ((packed))". This directive is GCC based (as I read somewhere here) and so not generally portable - which I would prefer. Example:

typedef struct __attribute__ ((packed)) {
    uint8_t     u8;  // (*)
    int16_t     i16; // (*)
    float       v;
    ...
    uint16_t    cs;  // (*)
}valset_t;           // valset_t is more often used 
valset_t   vs;

(*) values would use 4 bytes without the ((packed)) attribute, instead of one or two as desired. Byte-wise access for transmission with:

union{
    valset_t    vs;                     // value-set
    uint8_t     b[sizeof(valset_t)];    // byte array
}vs_u;

using vs_u.b[i] .

Is there a more portable solution in C to perform this task?

Upvotes: 1

Views: 570

Answers (1)

Lundin
Lundin

Reputation: 213266

Packing/padding isn't standardized, and therefore structs/unions are strictly speaking not portable. #pragma pack(1) is somewhat more common and supported by many different compilers (including gcc), but it is still not fully portable.

Also please note that padding exists for a reason, these structs with non-standard packing could be dangerous or needlessly inefficient on some systems.

The only 100% portable data type for storing protocols etc is an array of unsigned char. You can get only get fully portable structs if you write serializer/deserializer routines for them. This naturally comes at the expense of extra code. Example of deserialization:

valset_t data_to_valset (const unsigned char* data)
{
  valset_t result;
  result.something = ... ;
  ...
  return result;
}

In case of a certain network endianess, you can convert from network endianess to CPU endianess inside the same routine.

Please note that you have to type it out like in the function example above. You cannot write code such as:

unsigned char data[n] = ... ;
valset_t* vs = (valset_t*)data; // BAD

This is bad in multiple different ways: alignment, padding, strict aliasing, endianess and so on.

It is possible to go the other way around though, using a unsigned char* to inspect or serialize a struct byte by byte. However, doing so doesn't solve the issues of padding bytes or endianess still.

Upvotes: 2

Related Questions