2

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

Daniel
  • 403
  • 2
  • 15
  • 1
    The [`_Alignas(x)` directive](https://en.cppreference.com/w/c/language/_Alignas) is a good way to specify an alignment requirement. – Adrian Mole Jun 30 '22 at 12:41
  • My code has to be ANSI C90 compiler compatible. – Daniel Jun 30 '22 at 12:48
  • 2
    `(struct Meta_t*) mempool[123]` is still undefined behavior because of strict aliasing, regardless of alignment. – Lundin Jun 30 '22 at 12:53
  • I re-opened the question and added the C89 tag. – Lundin Jun 30 '22 at 12:54
  • @Lundin How could the strict aliasing violation be prevented? – Daniel Jun 30 '22 at 12:58
  • As far as I am aware, C90 does not provide any mechanism by which you can ensure (in the "C90 demands it" sense) that an array declared with one type is suitably aligned for *every* other type, including all structure types. You do, however, get such a guarantee from dynamic allocation, and you can get it for automatic and static allocation in C11 and later. – John Bollinger Jun 30 '22 at 13:35
  • "Because of 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" It's not unreasonable to demand that whoever ports the code adds a #pragma or attribute with the corresponding linker script to allocate your memory pool at a fixed aligned address in RAM. Leaving it floating at some unknown address inside `.bss` would be rather questionable though. – Lundin Jun 30 '22 at 14:04
  • The general best practice approach for any form of buffers (including the stack itself) in embedded systems is to keep them in separate memory segments and away from things they might tear down in case of overflow/underflow. – Lundin Jun 30 '22 at 14:05
  • Similarly, `static_assert((unsigned long)mempool % 4, "Misalignment of critical variables, bad memory map, bad!");` isn't unreasonable either, although not very portable. – Lundin Jun 30 '22 at 14:08

3 Answers3

1

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

_Alignas(uint64_t) uint8_t mempool[SIZE];
dbush
  • 205,898
  • 23
  • 218
  • 273
  • 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 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. – Daniel Jun 30 '22 at 12:46
1

(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++.)

Lundin
  • 195,001
  • 40
  • 254
  • 396
0

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.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • Why are all structure types necessary inside the union? Wouldn't it be aligned already if only one union element exists that requires the maximum alignment (like each type of struct)? – Daniel Jun 30 '22 at 15:07
  • @Daniel, because implementations may impose a stricter alignment requirement on structures than their members alone require. They may, separately, do the same for unions, but that's already accommodated in the above. – John Bollinger Jun 30 '22 at 15:10