Home > OS >  C: Portable way to define Array with 64-bit aligned starting address?
C: Portable way to define Array with 64-bit aligned starting address?

Time:07-01

For code that is compiled on various/unknown architectures/compilers (8/16/32/64-bit) a global mempool array has to be defined:

uint8_t mempool[SIZE];

This mempool is used to store objects of different structure types e.g.:

typedef struct Meta_t {
  uint16_t size;
  struct Meta_t *next;
  //and more
}

Since structure objects always have to be aligned to the largest possible boundary e.g. 64-byte it has to be ensured that padding bytes are added between those structure objects inside the mempool:

struct Meta_t* obj = (struct Meta_t*) mempool[123]   padding;

Meaning if a structure object would start on a not aligned address, the access to this would cause an alignment trap.

This works already well in my code. But I'm still searching for a portable way for aligning the mempool start address as well. Because without that, padding bytes have to be inserted already between the array start address and the address of the first structure inside the mempool.

The only way I have discovered so far is by defining the mempool inside a union together with another variable that will be aligned by the compiler anyways, but this is supposed be not portable.

Unfortunately for embedded platforms my code is also compiled with ANSI C90 compilers. In fact I cannot make any guess what compilers are exactly used. Because of this I'm searching for an absolutely portable solution and I guess any kind of preprocessor directives or compiler specific attributes or language features that were added after C90 cannot be used

CodePudding user response:

You can use _Alignas, which is part of the C11 standard, to force a particular alignment.

_Alignas(uint64_t) uint8_t mempool[SIZE];

CodePudding user response:

(struct Meta_t*) mempool will lead to undefined behavior for more reasons than alignment - it's also a strict aliasing violation.

The best solution might be to create a union such as this:

typedef union
{
  struct Meta_t my_struct;
  uint8_t bytes[sizeof(struct Meta_t)];
} thing;

This solves both the alignment and the pointer aliasing problems and works in C90.

Now if we do *(thing)mempool then this is well-defined since this (lvalue) access is done through a union type that includes an uint8_t array among its members. And type punning between union members is also well-defined in C. (No solution exists in C .)

CodePudding user response:

Unfortunately, this ...

This mempool is used to store objects of different structure types

... combined with this ...

I'm searching for an absolute portable solution and I guess any kind of preprocessor directives or compiler specific attributes or language features that were added after C90 cannot be used

... puts you absolutely up a creek, unless you know in advance all the structure types with which your memory pool may be used. Even the approach you are taking now does not conform strictly to C90, because there is no strictly-conforming way to determine the alignment of an address, so as to compute how much padding is needed.* (You have probably assumed that you can convert it to an integer and look at the least-significant bits, but C does not guarantee that you can determine anything about alignment that way.)

In practice, there is a variety of things that will work in a very wide range of target environments, despite not strictly conforming to the C language specification. Interpreting the result of converting a pointer to an integer as a machine address, so that it is sensible to use it for alignment computations, is one of those. For appropriately aligning a declared array, so would this be:

#define MAX(x,y) (((x) < (y)) ? (y) : (x))
union max_align {
    struct d { long l; } l;
    struct l { double d; } d;
    struct p { void *p; } p;
    unsigned char bytes[MAX(MAX(sizeof(struct d), sizeof(struct l)), sizeof(struct p))];
};
#undef MAX

#define MEMPOOL_BLOCK_SIZE sizeof(union max_align)
union maxalign mempool[(size   MEMPOOL_BLOCK_SIZE - 1) / MEMPOOL_BLOCK_SIZE];

For a very large set of C implementations, that not only ensures that the pool itself is properly aligned for any use by strictly-conforming C90 clients, but it also conveniently divides the pool into aligned blocks on which your allocator can draw. Refer also to @Lundin's answer for how pointers into that pool would need to be used to avoid strict aliasing violations.

(If you do happen to know all the types for which you must ensure alignment, then put one of each of those into union max_align instead of d, l, and p, and also make your life easier by having the allocator hand out pointers to union max_align instead of pointers to void or unsigned char.)

Overall, you need to choose a different objective than absolute portability. There is no such thing. Avoiding compiler extensions and language features added in C99 and later is a great start. Minimizing the assumptions you make about implementation behavior is important. And where that's not feasible, choose the most portable option you can come up with, and document it.


*Not to mention that you are relying on uint8_t, which is not in C90, and which is not necessarily provided even by all C99 and later implementations.

  • Related