1

I have an 18 byte struct in C++. I want to read 18 bytes of data from a file straight into this struct. However, my C++ compiler pads the struct to be 20 bytes (4 byte aligned). This is relatively easy to get around for just my compiler alone but I would prefer to use a method that is more reliable cross-platform/cross-compiler.

This is the struct:

struct Test {
    uint8_t a;
    uint8_t b;
    uint8_t c;
    uint16_t d;
    uint16_t e;
    uint8_t f;
    uint16_t g; 
    uint16_t h; 
    uint16_t i; 
    uint16_t j; 
    uint8_t  k;  
    uint8_t  l;
};

I could add bytes to the front of the struct to guarantee it to be 32 bytes which would be a valid alignment on most systems, however I don't know if that would actually work with how structs need their elements to be naturally aligned.

Any help on this would be great but I could always end up copying the bytes manually into the attributes .

  • 1
    you can get rid of padding for this particular `struct` by move `f` to after `c`. If you cannot control how you receive data from input file, you should read and adjust. The padding is there for a purpose, certain CPUs have strict alignment requirement. – Lance Oct 30 '22 at 19:18
  • or you may take a look at: https://stackoverflow.com/questions/8933707/locally-disable-padding – Lance Oct 30 '22 at 19:21
  • 1
    100% guaranteed "reliable cross-platform/cross-compiler" solution. Read into a `unsigned char[18]`, extract all the fields, byte by byte. Are you familiar with bitwise arithmetic techniques in C++? – Sam Varshavchik Oct 30 '22 at 19:25
  • @SamVarshavchik That is definitely the best way to do it. I wanted to see if it could read the bytes in "dynamically" even though the data is in the same format each time but it seems like its too unsafe and unreliable. I'll probably just do it that way. –  Oct 30 '22 at 19:30

2 Answers2

1

If you are writing for cross-platform and cross-compiler, forget the direct read. Just read byte-by-byte. If profiling indicates you need further optimization, either play with the underlying stream buffering or read into a byte array and extract each piece.

This also eliminates system endianness problems.

// extract byte values
t.a = f.get();
t.b = f.get();

...

// extract a little-endian value
t.d = f.get();
t.d = t.d | (f.get() << 8);

...

// check for success or failure
if (!f) ...

Block read via a byte array:

unsigned char buffer[ 18 ];
if (!f.read( (char *)buffer, sizeof(buffer) ))
  ...

t.a = buffer[0];
...
t.d = buffer[3] | (buffer[4] << 8);
...

Again, profile before deciding there is an issue.

Dúthomhas
  • 8,200
  • 2
  • 17
  • 39
1

You have several options, and as usual, you should choose whatever best fits your needs:

  1. as stated before, don't read/write directly from/to memory, instead write each field separately (kind of how Java people would).
    This is the, I think, most portable, but WAY slower than the later methods.

  2. reorder the struct to match normal alignment (good practice anyway)
    in your example:

struct Test {
    uint8_t a;
    uint8_t b;
    uint8_t c;
    uint8_t f; // moved
    uint16_t d;
    uint16_t e;
//  uint8_t f;
    uint16_t g; 
    uint16_t h; 
    uint16_t i; 
    uint16_t j; 
    uint8_t  k;  
    uint8_t  l;
    uint16_t spare;
};

Note: it still have 2 byte padding, but not in the middle :)

  1. use #pragma pack(push, 1) on the struct to tell the compiler to NOT align the bytes

Note: you may need to place multiple #pragma to support different compilers

#pragma pack(push, 1)
struct Test {
    uint8_t a;
    uint8_t b;
    uint8_t c;
    uint16_t d;
    uint16_t e;
    uint8_t f;
    uint16_t g; 
    uint16_t h; 
    uint16_t i; 
    uint16_t j; 
    uint8_t  k;  
    uint8_t  l;
    uint16_t spare;
};
#pragma pack(pop)

I'd like to add that proper alignment helps the CPU process faster, therefore, you don't want to force pack = 1 on all structs... only those intended to be transmitted or received via communication channel.

Tomer W
  • 3,395
  • 2
  • 29
  • 44