malloc and C alignment: is this hand-made optimization safe?

I put the whole code but, of course, it’s in different files (.h and .c files)

typedef unsigned char ubyte;
typedef unsigned int uint;
#include<stdbool.h>

typedef struct Cube {
    ubyte n;
    ubyte e;
    ubyte s;
    ubyte w;
} Cube;

typedef struct Piece {
    Cube c;
    bool is_main;
    char offset_n;
    char offset_s;
} Piece;

typedef struct Block {
    ubyte total;
    Piece* pieces;
} Block;

Block *block_create(uint nb_pieces) {
    Block *block = malloc(sizeof(Block) + (sizeof(Piece) * nb_pieces));
    block->pieces = (Piece *) (&block + sizeof(Block));
    return block;
}

I am just wondering if this line of code: block->pieces = (&block + sizeof(Block)); will be always safe. I mean: can we be sure that, immediately after the sizeof(Piece), we will have precisely (sizeof(Piece) * nb_pieces)? Are we sure there will never be an alignment problem (ie maybe if it’s 64-bits aligned, the memory for sizeof(Block) will be less than 8 bytes and the block->pieces should not point to exactly sizeof(Block), but "sizeof(Block) 64 bits-aligned").

I hope I’m clear enough.

>Solution :

First, this isn’t doing what you expect, and for multiple reasons:

block->pieces = (&block + sizeof(Block));

First, &block is the address of the variable block, not the contents of block, and has type block **. At most, you can safely add 1 this pointer value because anything more will create a pointer past the end of this variable which is invalid.

So then you would need to change &block to block. That still won’t do what you expect because pointer arithmetic increments the raw address by multiples of the object size. So adding sizeof(Block) to this is not moving up 1 array element but moving up sizeof(Block) array elements.

To fix this you would need block + 1. Now you need to start worrying about alignment. For your array of Pieces to be aligned properly, you would need to check if _Alignof(Block) and _Alignof(Piece) are the same. If not, you would need to add padding bytes:

int padding = 0;
if (_Alignof(Block) % _Alignof(Piece) != 0) {
    padding = _Alignof(Piece) - (_Alignof(Block) % _Alignof(Piece));
}
Block *block = malloc(sizeof(Block) + padding + (sizeof(Piece) * nb_pieces));
block->pieces = (Piece *)((char *)(block + 1) + padding);

Of course, you can avoid all this by making the pieces member a flexible array member:

typedef struct Block {
    ubyte total;
    Piece pieces[];
} Block;

And the allocation by itself will be enough.

Leave a Reply