Why reserve memory in the structure?

Viewed 400

I often see structures in the code, at the end of which there is a memory reserve.


struct STAT_10K4
{
     int32_t npos; // position number
     ...
     float Plts;
             Pxts;
     float Plto [NUM];
     uint32_t reserv [(NUM * 3)% 2 + 1];
};
  1. Why do they do this?
  2. Why are some of the reserve values dependent on constants?
  3. What can happen if you do not make such reserves? Or make a mistake in their size?
2 Answers

This is a form of manual padding of a class to make its size a multiple of some number. In your case:

uint32_t reserv [(NUM * 3)% 2 + 1];

NUM * 3 % 2 is actually nonsensical, as it would be equivalent to NUM % 2 (not considering overflow). So if the array size is odd, we pad the struct with one additional uint32_t, on top of + 1 additional ones. This padding means that STAT_10K4's size is always a multiple of 8 bytes.

You will have to consult the documentation of your software to see why exactly this is done. Perhaps padding this struct with up to 8 bytes makes some algorithm easier to implement. Or maybe it has some perceived performance benefit. But this is pure speculation.

Typically, the compiler will pad your structs to 64-bit boundaries if you use any 64-bit types, so you don't need to do this manually.


Note: This answer is specific to mainstream compilers and x86. Obviously this does not apply to compiling for TI-calculators with 20-bit char & co.

This would typically be to support variable-length records. A couple of ways this could be used will be:

1 If the maximum number of records is known then a simple structure definition can accomodate all cases.

2 In many protocols there is a "header-data" idiom. The header will be a fixed size but the data variable. The data will be received as a "blob". Thus the structure of the header can be declared and accessed by a pointer to the blob, and the data will follow on from that. For example:

typedef struct
{
    uint32_t messageId;
    uint32_t dataType;
    uint32_t dataLenBytes;
    uint8_t data[MAX_PAYLOAD];
}
tsMessageFormat;

The data is received in a blob, so a void* ptr, size_t len.

The buffer pointer is then cast so the message can be read as follows:

tsMessageFormat* pMessage = (psMessageFormat*) ptr;
for (int i = 0;  i < pMessage->dataLenBytes;  i++)
{
    //do something with pMessage->data[i];
}

In some languages the "data" could be specified as being an empty record, but C++ does not allow this. Sometimes you will see the "data" omitted and you have to perform pointer arithmetic to access the data.

The alternative to this would be to use a builder pattern and/or streams.

Windows uses this pattern a lot; many structures have a cbSize field which allows additional data to be conveyed beyond the structure. The structure accomodates most cases, but having cbSize allows additional data to be provided if necessary.

Related