Chris Davies
Chris Davies

Reputation: 39

How to fix allocation size of a struct

I have a struct

typedef struct
{
    int A ;
    int B ;
    …
} SomeStruct ;

I have an instance of SomeStruct that I want to persist to Flash memory which has a sector size of 512 bytes. What GCC attribute can I apply to that instance such that the allocation size is a multiple of 512 bytes?

The only options I can think of are :-

1) Pad the struct to make it exactly 512 bytes. This requires recalculation every time a field is added to the struct. No compiler warnings when I get it wrong. Also struct is larger than needed with normal initialisation, copying etc.

2) Place the variable in a separate Linker section. This provides full protection and warnings, but gets a bit tedious if multiple variables are used.

3) Make a union of the struct and a 512 byte array. Copes with adding extra fields until the struct is greater than 512 bytes, then fails without any warnings.

Upvotes: 2

Views: 718

Answers (3)

alk
alk

Reputation: 70901

Referring 1:

#include <assert.h>

#define FLASH_BYTES (512)

#pragma pack(1)

struct flash
{
  struct data
  {
    int i;
    char c;
    ...
  };
  char pads[FLASH_BYTES - sizeof (struct data)];
};

#pragma pack()

int main(void)
{
  assert(sizeof (struct flash) == FLASH_BYTES);

  ...

The assert might even not be necessary because if the result

  FLASH_BYTES - sizeof (struct data)

is negative any GCC should issue an error. To make sure it will be negative cast the result of the sizeof operation to any signed integer, like for example so:

  FLASH_BYTES - (int) sizeof (struct data)

So trying to compile this

#pragma pack(1)

struct flash
{
  struct data
  {
    int i;
    char c[FLASH_BYTES];
  };
  char pads[FLASH_BYTES - (int) (sizeof (struct data))];
};

#pragma pack()

int main(void)
{
}

You should be giving you something like:

main.c:14:12: error: size of array ‘pads’ is negative
       char pads[FLASH_BYTES - (int) sizeof (struct data)];

Upvotes: 3

Bob
Bob

Reputation: 2616

You can try something like this (though it is a dirty bit trick)

#define ROUND_UP_512(x) ((x) + 511 & ~511)

struct myStruct {
    // put whatever
};

union myUnion{
    myStruct s;
    char ensureSize[ROUND_UP_512(sizeof(myStruct))];
};

in this case the size of "myUnion" is guaranteed to be a multiple of 512 that is greater than or equal to the size of "myStruct"

Upvotes: 0

fifoforlifo
fifoforlifo

Reputation: 774

A portable solution is to define a union of SomeStruct, with a char array whose size is calculated to meet the necessary alignment.

typedef struct
{
    int A;
    int B;
    char c[512];
} SomeStruct;

#define STORAGE_ALIGNMENT 512
typedef union
{
    SomeStruct data;
    char paddedData[((sizeof(SomeStruct) + STORAGE_ALIGNMENT - 1) / STORAGE_ALIGNMENT) * STORAGE_ALIGNMENT];
} SomeStructAligned;

Online running version (Coliru) here

The sizing formula is well known and works for any integer. Since this is a power-of-2 you could also simplify it down to the form (sizeof(SomeStruct) + (STORAGE_ALIGNMENT - 1)) & ~(STORAGE_ALIGNMENT - 1)) == (sizeof(SomeStruct) + 0x1ff) & ~0x1ff). In practice you may need ~size_t(0x1ff) on the rightmost term to ensure portability to 64-bit machines; since 0x1ff is an int (32-bit), ~0x1ff results in a 64-bit 0x00000000fffffe00 value instead of the desired 0xFFFFFFFFfffffe00 mask.

Sub-Optimal Approach

An alternative approach could have been to define a wrapper struct containing your original data plus some automatically calculated padding.

typedef struct
{
    int A;
    int B;
} SomeStruct;

#define STORAGE_ALIGNMENT 512
typedef struct
{
    SomeStruct data;
    char padding[(STORAGE_ALIGNMENT) - (sizeof(SomeStruct) % STORAGE_ALIGNMENT)];
} SomeStructAligned;

Online running version (Coliru) here.

However, the above is not perfect: if sizeof(SomeStruct) is a multiple of 512, then sizeof(padding) will be 512, wasting a quantum of storage. Whereas the union never wastes space.

Upvotes: 0

Related Questions