Memory alignment

The Secure Elements on top of which the BOLOS Operating System and the associated applications run imply a 32-bit alignment. This paragraph aims at explaining the C associated development constraints.

Alignment concept

The memory alignment is a concept which applies to memory and pointers:

  • A memory address is ‘b-bits aligned’ when it is a multiple of b/8, b/8 being a power of 2,
  • A memory address is said to be aligned when the data referenced by said address is b bits long, and said address is b-bits aligned,
  • A pointer is ‘aligned’ when it points on aligned memory,
  • A pointer is ‘unaligned’ when it points on unaligned memory.

Alignment constraints for basic types and structures

Implementing C source code with types and structures is not functionally impacted by the 32-bit alignment, except for potentially wasting a few bytes without even noticing.

It might be important to be aware of this paragraph contents when it comes to writing memory-efficient structures, once the application is compiled and loaded onto a device.

Within any application source code, the aligment of basic types will be considered as follows, at compilation time:

  • char / unsigned char / int8_t / uint8_t : 8-bit aligned,
  • short / unsigned short / int16_t / uint16_t: 16-bit aligned,
  • int / unsigned int / int32_t / uint32_t: 32-bit aligned,
  • any pointer: 32-bit aligned.

Please note that 8-bit aligned means that there is actually no alignment constraint.

The compiler will add padding in any structure which is not aligned by design, in order to respect:

  • The alignment of each field associated to their respective length,
  • The alignment of the whole structure, which shall have a total length, padding included, multiple of the largest field’s length.

For instance the following structure is 8 bytes long before compilation:

// Before compilation
struct Example1
{
    char    field_1;
    short   field_2;
    int     field_3;
    char    field_4;
};

However during compilation, the structure is modified to ensure the alignment, and will thus be 12 bytes long:

// After compilation
struct Example1
{
    char    field_1;
    // Padding added for field_2 to start on a 16-bit aligned address
    char    padding_1;
    short   field_2;
    // With padding_1 being added, field_3 will start on a 32-bit aligned
    // address and no padding is required here.
    int     field_3;
    char    field_4;
    // The structure is aligned to the number of bits corresponding to the
    // largest field's alignement, in this case, due to field_3, 32-bits.
    // For the structure to be 32-bit aligned, it needs 3 more bytes of padding.
    char    padding_2[3];
};

In this example, it is possible to reorganize the structure’s fields to avoid alignment-induced padding, but sometimes padding will not be avoidable.

One can order the structure fields according to their length in decreasing order:

// Before compilation
struct Example1_reordered
{
    int     field_3;
    short   field_2;
    char    field_1;
    char    field_4;
};
// After compilation
struct Example1_reordered
{
    int     field_3;
    // No need for padding since field_2 is already on a 16-bit aligned address.
    short   field_2;
    // No need for padding for char types.
    char    field_1;
    char    field_4;
    // No need for padding since the structure is 8 bytes long and thus, its length
    // is already a multiple of its largest field's length.
};

One can also order the structure fields to make sure the minimum amount of padding bytes will be added by the compilation phase:

// Before compilation
struct Example1_reordered_other_way
{
    int     field_3;
    char    field_1;
    char    field_4;
    short   field_2;
};
// After compilation
struct Example1_reordered_other_way
{
    int     field_3;
    // No need for padding for char types.
    char    field_1;
    char    field_4;
    // No need for padding since field_2 is already on a 16-bit aligned address.
    short   field_2;
    // No need for padding since the structure is 8 bytes long and thus, its length
    // is already a multiple of its largest field's length.
};

Alignment constraints for pointers

Using pointers within C source code might be functionally impacted by the 32-bit alignment in a specific case: when the pointer points on a memory area which type differs from the pointer, and is dereferenced.

Dereferencing unaligned pointers within an application stalls the device.

Usually, pointers are used to store the address of an element which type corresponds to the pointer one, and for simple example:

uint16_t *pointer;
uint16_t array[10];

// Pointer positioning is perfectly fine.
pointer = &array[3];

// Dereferencing this pointer is also perfectly fine, since the
// pointed memory is aligned in accordance with the pointer type.
*pointer = 0x0001;

However, if we use a pointer with a specific type to store the address of a memory area declared with another type (usually with an alignment-related size less than the pointer one), it can lead to hardware faults and stall the device:

uint16_t *pointer;
uint8_t array[10];

// Case where it will work even if not advised.

// Pointer positioning is fine.
pointer = (uint16_t*)&array[2];

// Dereferencing this pointer is also fine: the pointed memory is aligned
// in accordance with the pointer type (because the offset 2 in the array variable
// is a multiple of 16 bits).
if (*pointer == 0x0001) {
    do_something();
}

// Case where it will stall the device.

// Pointer positioning is fine, but it is unaligned.
pointer = (uint16_t*)&array[3];

// Dereferencing this pointer will stall the device: the pointed memory is not aligned
// in accordance with the pointer type (because the offset 3 in the array variable
// is not a multiple of 16 bits).
if (*pointer == 0x0001) { /* This dereferencing stalls the device. */
    do_something();
}

The same reasoning applies to pointing on structures:

// Same example as within the previous paragraph, being ordered
// makes it 8 bytes long.
struct Example1_reordered
{
    int     field_3;
    short   field_2;
    char    field_1;
    char    field_4;
};

Example1_reordered *pointer;
uint8_t array[32];

// Case where it will work even if not advised.

// Pointer positioning is fine.
pointer = (Example1_reordered*)&array[8];

// Dereferencing this pointer is also fine: the pointed memory is aligned
// in accordance with the pointer type (because the offset 8 in the array variable
// is a multiple of the structure's size after compilation).
if (pointer->field_2 == 0x0001) {
    do_something();
}

// Case where it will stall the device.

// Pointer positioning is fine, but it is unaligned.
pointer = (Example1_reordered*)&array[3];

// Dereferencing this pointer will stall the device: the pointed memory is not aligned
// in accordance with the pointer type (because the offset 3 in the array variable
// is not a multiple of the structure's size after compilation).
if (pointer->field_2 == 0x0001) { /* This dereferencing stalls the device. */
    do_something();
}

Unaligned pointers can thus occur in cases where a pointer:

  • declared as positioning on some data type (or structure)
  • is used to point on a memory area actually containing another type of data,
  • and is dereferenced.

In order to produce C source code robust to alignment constraints, one need to avoid using pointers in such a way.