64

I have a structure that must pad out to 64K to fit perfectly in an embedded project, so that it fills out a flash block. So there is a #define that adds up the elements in the structure using sizeof() and determines how big the pad[] at the end needs to be to cause the total size to be 64K.

For example :

#define SIZE_OF_MY_PAD (0x10000 - (sizeof(uint16_t) + sizeof(uint8_t)*32 + ... ))

typedef struct {
  uint16_t firstElement;
  uint8_t  secondElementArray[32];
  ...
  uint8_t pad[SIZE_OF_MY_PAD];
};

This has worked great for a long time until suddenly we don't need the pad at all in certain build configurations because it is already exactly 64k. This causes the code to fail because our compiler (not GCC) does not allow pad[0].

I have tried various ways to create a preprocessor value that I can use in an #if statement when this is detected, but it always fails because although sizeof() is legal in #define, it is not legal in #if.

XouDo
  • 945
  • 10
  • 19
RD Florida
  • 741
  • 4
  • 4
  • 6
    Add a static assert on the `sizeof` of the `struct`. You can't adjust the size of the padding in compile time, but you can make it to fail the compilation in case it is not correct. – Eugene Sh. Apr 07 '21 at 19:05
  • 8
    While a good question, this is something probably better solved in the _linker_, not the compiler. – pipe Apr 08 '21 at 08:37
  • @rd-florida Can you identify what compiler you use? Is it C11 compatible? – Ross Presser Apr 08 '21 at 13:11
  • 9
    @pipe: I disagree. Keep the linker out of this. You sanity will thank you. – Joshua Apr 08 '21 at 21:14
  • 3
    I would recommend to place this code in a dedicated section (`#pragma section`) and then place the section where you want in the linker script. – prapin Apr 09 '21 at 19:49

1 Answers1

119

This problem can be solved without any need for preprocessor with a help of anonymous structs introduced in C11.

Define the flash type as a union that contains members embedded into anonymous struct. Make char _pad[0x10000] the other member of the union to force the total size of the introduced type.

typedef union {
    struct {
        uint16_t firstElement;
        uint8_t  secondElementArray[32];
        float thirdElement;
    };
    char _pad[0x10000];
} flash_t;

This solution is robust to any modifications to the layout of the struct members. Moreover, this avoids problem of defining zero-length array that is technically forbidden by C standard (though allowed in GCC). Additionally, one can add a static assert to check if the maximal size of the flash got overflown.

Example program:

#include <stdio.h>
#include <stdint.h>
#include <stddef.h>

typedef union {
    struct {
        uint16_t firstElement;
        uint8_t  secondElementArray[32];
        float thirdElement;
        // int kaboom[20000]; // will trigger assert if uncommented
    };
    char _pad[0x10000];
} flash_t;

_Static_assert(sizeof(flash_t) == 0x10000, "Oops, flash_t got too large");

int main() {
    flash_t flash;
    printf("offsetof(flash.firstElement) = %zi\n", offsetof(flash_t, firstElement));
    printf("offsetof(flash.secondElementArray) = %zi\n", offsetof(flash_t, secondElementArray));
    printf("offsetof(flash.thirdElement) = %zi\n", offsetof(flash_t, thirdElement));
    printf("sizeof(flash) = %zi\n", sizeof flash);
    return 0;
}

Produces expected output:

offsetof(flash.firstElement) = 0
offsetof(flash.secondElementArray) = 2
offsetof(flash.thirdElement) = 36
sizeof(flash) = 65536

EDIT

  • As suggested in the comment the union member _pad could be renamed to _rawData because semantics of _pad differs from pad in the question.

  • If a member pad within the type is required then one could add it as a flexible member at the end of anonymous struct.

typedef union { struct { ...; uint8_t pad[]; }; char _rawData[0x10000]; } flash_t;
tstanisl
  • 13,520
  • 2
  • 25
  • 40
  • 1
    Why is `offsetof(thirdElement)` 36 and not 34? – theonlygusti Apr 08 '21 at 10:21
  • 11
    @theonlygusti, it's compiler dependant. It tries to place `float` at the address divisible by 4. Number 36 it the first one after 34. – tstanisl Apr 08 '21 at 10:36
  • 2
    Probably also worth renaming from `pad` to something like `rawData` at the same time - otherwise the code to fill the `pad` with known data after filling in the rest of the data structure might do completely the wrong thing. – Steve Apr 08 '21 at 15:37
  • 1
    Without anonymous structs it would work as well, except that one would need to write `flash.data.firstElement` or whatever because the intermediate structure is now named, right? – Peter - Reinstate Monica Apr 08 '21 at 20:23
  • @Peter-ReinstateMonica, yes. Using anonymous struct lets one use `flash.firstElement`. This approach let minimize changes to the existing code. – tstanisl Apr 08 '21 at 20:38
  • @tstanisl I don't have a reference at hand, but iirc it's in the standard that struct members a padded to a multiple of their size. – jaskij Apr 08 '21 at 21:11
  • @JanDorniak: Not so. I have a 10 byte primitive; can you imagine how that pads? struct members are padded to the alignment of the next member, and the last struct member is padded to at least the alignment of the first (so that arrays of the struct work). – Joshua Apr 08 '21 at 21:17
  • 2
    @JanDorniak, AFAIK, the C standard only requires offsets of consecutive members to be strictly increasing, and that the offset of the first member is 0. Everything else is implementation-specific. – tstanisl Apr 08 '21 at 21:18
  • Better refer back to `_pad` than hardcoding the same magic number twice. – Deduplicator Apr 08 '21 at 22:49
  • @Deduplicator, the static assert for `sizeof(flash_t) == 0x10000` explicitly expresses OP intention, that the size of the type is exactly 64kB. IMO, it should be left as it is. – tstanisl Apr 09 '21 at 07:59
  • 1
    `_pad` already hardcodes the wanted size. Having the (hopefully) same magic number in multiple places goes against single-source-of-truth. size of flash equals size of raw data (steve is right about the name) expresses the intent as well, without repeating some unmotivated magic number. – Deduplicator Apr 09 '21 at 10:32
  • 1
    @JanDorniak: struct layout rules are defined by an ABI for the platform that lets different compilers agree with each other by following the same layout rules. (In ISO C terms, it's implementation-defined, with multiple implementations merely choosing to follow the same rules.) A common rule is to align each member to its `alignof(T)` (not sizeof) relative to the start of the struct, and for the overall struct to inherit the max alignof of any member. But MSVC notably has more complex rules: [Why is the "alignment" the same on 32-bit and 64-bit systems?](https://stackoverflow.com/q/55920103) – Peter Cordes Apr 09 '21 at 21:41
  • @PeterCordes I did some reading since then and at least objects being on the multiple of `alignof(T)` is in C11. But the language of the alignment section seemed relatively loose. – jaskij Apr 09 '21 at 22:10
  • @JanDorniak: Indeed, if your C implementation gets it right, you'll never find a `uint64_t` with less than `alignof(uint64_t)` alignment even in a struct, although `alignof(uint64_t)` might only be 4 bytes in some 32-bit ABIs. (Unless you use extensions like `__attribute__((packed))` to leave out struct padding, in which case it might be undefined behaviour to take a pointer to that misaligned `uint64_t` instead of accessing it only as a struct member where the compiler can the packed.) And yes, ISO C doesn't define an ABI; it leaves lots of room for implementations (ABIs) to make choices. – Peter Cordes Apr 09 '21 at 22:14
  • @JanDorniak: Of course real life doesn't always strictly follow the ISO C standard; e.g. as I mentioned [in a comment on that linked answer](https://stackoverflow.com/questions/55920103/why-is-the-alignment-the-same-on-32-bit-and-64-bit-systems#comment98509735_55920216), MSVC has `alignof(int64_t) == 8`, but that's apparently only the preferred alignment, not the actual minimum that it requires or that it ensures for locals on the stack, or for struct members. (C++ and C rules for the meaning of `alignof()` are I think compatible.) – Peter Cordes Apr 09 '21 at 22:16
  • 1
    @PeterCordes I've had an MCU crap out on me because of bad memory access, through no fault of my own (vendor library did a cast to wider type without checking address). Might be I mixed it up with C++ standard, which iirc is a bit stricter here. – jaskij Apr 09 '21 at 22:17
  • @JanDorniak: sounds like a buggy library, or under-documented requirement for an aligned `char *` or whatever it was, unrelated to structs. It's already strict-aliasing UB and possible alignment UB for a function taking a `short *p` arg to cast it to `uint64_t *` and dereference, unless that `short *` arg was originally pointing at `uint64_t` objects and you're just casting it back. In GNU C and C++, you can tell the compiler about this with `typedef uint64_t aliasing_unaligned_u64 __attribute__((aligned(1), may_alias));` (see [this answer](https://stackoverflow.com/a/47512025/224132)) – Peter Cordes Apr 09 '21 at 22:27
  • 1
    @PeterCordes buggy library. It was an optimization, fair and square (it literally halved the number of CPU load during SPI transfer), but the vendor carelessly cast `uint8_t*` to `uint16_t*`. Add that I was pointing to an odd index in a buffer and it was guaranteed to happen. UB in terms of C, error interrupt in Cortex-M. – jaskij Apr 09 '21 at 23:15