2

I have one struct that is mapped to a byte array. But I found that it seems not all fields are correctly mapped. I'm wondering if this is an alignment problem or a bug.

struct _sg64_struct
{
  SG64_PCSC_TLV_HEADER header; /* This is 2 bytes */
  gint8 id_perso;
  gint8 status;
  gint8 fare_type;  
  gint16 fare_zone;     
  gint8 support_type;   
  gchar loginPerso[15];
};

I have this structure to be mapped to this bytes:

41150002000000000E2020202020202020202020202020

But I found that support_type gets 0x0E value instead of 00 as is supposed to be. If we map it

    struct _sg64_struct
{
  SG64_PCSC_TLV_HEADER header; 4115
  gint8 id_perso; 00
  gint8 status;   02
  gint8 fare_type;  00
  gint16 fare_zone;     0000
  gint8 support_type;   00
  gchar loginPerso[15]; E2020202020202020202020202020
};

But I get as I said support_type with 0E. Strange thing is that if I map fare_zone as two bytes it works.

struct _sg64_struct
{
  SG64_PCSC_TLV_HEADER header; /* This is 2 bytes */
  gint8 id_perso;
  gint8 status;
  gint8 fare_type;  
  gint8 fare_zone[2];   
  gint8 support_type;   
  gchar loginPerso[15];
};

This structure works. But the question is why? Can we trust the compiler types?

  • 2
    Padding issue? See [Why isn't sizeof for a struct equal to the sum of sizeof of each member?](https://stackoverflow.com/questions/119123/why-isnt-sizeof-for-a-struct-equal-to-the-sum-of-sizeof-of-each-member) – Some programmer dude Nov 21 '17 at 13:00
  • 3
    Yes, this looks like a typical alignment behavior. Unless you really have to for very good reasons you shouldn't map a struct to a byte sequence, just decode the byte stream into the struct manually. It's the most portable and safe way to do things. – Art Nov 21 '17 at 13:09
  • 1
    Many compilers support `#pragma`s to modify struct packing. If you are OK with making the code less portable you should look up `#pragma pack`. – Klas Lindbäck Nov 21 '17 at 13:12
  • 2
    Notice that `#pragma pack` is a machine gun readily pointed at your foot. – Antti Haapala -- Слава Україні Nov 21 '17 at 13:18
  • You expect the fare_zone member to have an offset of 5. Default alignment rules for a 16-bit integer gives it an offset of 6 by inserting one byte of padding. That keeps processors happy that don't have a multiplexer that can shuffle the mis-aligned bytes. Compilers have a non-standard extension to disable this padding, like `__attribute__ ((packed))`. You need to fret about endian-ness as well, no simple fix for that. – Hans Passant Nov 21 '17 at 13:41
  • In fact we are dealing with few different architectures. One is x86_64, then ARM32 and smartcards, and desfire... So yes, I think this kind of alignement errors are a gun pointing your foot. But that's the way was done and I have to deal with it best way. So what's better? Pragma, manually map or what? – Gonzalo Aguilar Delgado Nov 22 '17 at 08:21
  • @Art But that's the way of serializing to disk in C. So if I cannot save and load structures directly why do I want a structure? I would map the bytes to stand alone variables, that's all. – Gonzalo Aguilar Delgado Jan 25 '18 at 08:30

2 Answers2

3

It doesn't work because there is padding inside the structure. This is a very bad practice. The assumption that the offset of a struct member is simply the sum of all preceeding members' sizes is wrong.

unwind
  • 391,730
  • 64
  • 469
  • 606
2

The type of each member of the structure usually has a default alignment, meaning that it will, unless otherwise requested by the programmer, be aligned on a pre-determined boundary.

gint8

typedef signed char gint8;

A signed integer guaranteed to be 8 bits on all platforms.

and

gint16

typedef signed short gint16;

An unsigned integer guaranteed to be 8 bits on all platforms.

When you have gint16 type of fare_zone in your structure, the compiler is padding struct _sg64_struct with 1 byte to align fare_zone.

So, this is happening with your structure when the fare_zone is of type gint16:

struct _sg64_struct
{
  SG64_PCSC_TLV_HEADER header;  // 2 bytes  4115
  gint8 id_perso;               // 1 byte   00 
  gint8 status;                 // 1 byte   02
  gint8 fare_type;              // 1 byte   00
  gchar pad[1];         <------ // 1 byte   00 (compiler is padding 1 byte to align fare_zone, as short type are 2-byte aligned)
  gint16 fare_zone;             // 2 byte   0000   
  gint8 support_type;           // 1 byte   0E
  gchar loginPerso[15];         // 15 byte  2020202020202020202020202020
};

And that's why support_type gets 0x0E value instead of 00.

When you have gint8 type of fare_zone in your structure, the compiler is padding size of struct _sg64_struct with 1 byte to alignment boundary and doing trailing padding.

struct _sg64_struct
{
  SG64_PCSC_TLV_HEADER header; // 2bytes     4115
  gint8 id_perso;              // 1 byte     00
  gint8 status;                // 1 byte     02
  gint8 fare_type;             // 1 byte     00
  gint8 fare_zone[2];          // 2 byte     0000 (no alignment required as the char types are 1 byte aligned)
  gint8 support_type;          // 1 byte     00
  gchar loginPerso[15];        // 15 byte    0E2020202020202020202020202020
  gchar pad[1];        <------ // Compiler is padding 1 byte to alignment boundary of structure
};

And that's why this structure works.

I would suggest not to map the structure with the sequence of bytes as the alignment of structure members might be vary based on the compiler and underlying platform.


Additional note:

Every modern compiler will automatically use data structure padding depending on architecture. Some compilers even support the warning flag -Wpadded which generates helpful warnings about structure padding. These warnings help the programmer take manual care in case a more efficient data structure layout is desired.

-Wpadded

Warn if padding is included in a structure, either to align an element of the structure or to align the whole structure. Sometimes when this happens it is possible to rearrange the fields of the structure to reduce the padding and so make the structure smaller.

So, if your compiler supports warning flag -Wpadded, try compiling your code with it. That will help you in understanding the padding included in a structure by the compiler.

H.S.
  • 11,654
  • 2
  • 15
  • 32
  • Great explanation. Since I have to deal with it and using gcc on all architectures I will try to enable the -Wpadded and carefully pad on each architecture. Thank you for the response. – Gonzalo Aguilar Delgado Nov 22 '17 at 08:24