0

This sounds straightforward enough, but I'm struggling to find a good answer.

Suppose I declare a C++ class with private members that collectively occupy an odd number of bytes. Say...

class Foo {
private:
    uint8_t bitflags;
    uint32_t millis;
}

or

class Foo {
private:
    uint8_t rawData[3];
}

If I compile it using GCC or Clang (possibly, wrapped inside the Arduino IDE, or multiply-wrapped inside PlatformIO, CMake, and an IDE like CLion), is there any likely scenario where the compiler (when targeting 8-bit AVR... specifically, the ATmega2560 on an Arduino Mega2560) would be inclined to add padding bytes to the portion of the class stored in SRAM? Say... between bitflags and millis, or between an instance of Foo and a uint64_t right after it?

If yes, what do I need to use to tell the compiler, "absolutely, positively DO NOT pad variables in SRAM (and, if you find yourself in a situation where it's seemingly unavoidable, at least output a prominent compile-time warning, if not an outright compilation-stopping error)?

Then... if I do that... are there any negative consequences to doing that with code that's targeting (and ultimately running upon) an 8-bit AVR?

As far as I know, 8-bit AVR has never had specific opcodes for copying a pair of consecutive bytes between registers and SRAM (I think, because Atmel literally ran out of opcode mapping space, and had nowhere to put hypothetical new opcodes like, "ldw rz, $f00d"), and movw only applied to pairs of registers... so, in theory, there's no reason for the compiler to "optimize" SRAM access to be word-aligned.

That said... I suppose it's not inconceivable that a compiler might default to word-alignment (even on platforms where it doesn't matter), just for the sake of ABI consistency between different platforms.

Before someone suggests, "try it and see!"... I've gotten far enough into learning C++ to know that with C++, you can't trust "compiles without errors and passes unit tests on my computer, with {this} IDE" as a general-purpose proof-of-correctness for C++ involving "implementation-defined" or "undefined" behavior. AFAIK, padding behavior is "implementation-defined".

4
  • Re “padding behavior is ’implementation-defined’”: My understanding is that it is defined by the ABI: the padding rules used in a compiled library must match those of the calling code. So you can try and see. Sep 5, 2022 at 18:57
  • It didn't seem completely nuts to me that they could have chosen to align words in structs because they are passed in registers (if small enough) in the calling convention and because the pairs used by MOVW are always aligned in the sense that they're even:odd pairs (which also become aligned in the memory mapped register file (when it is mapped). That said, it doesn't appear (from tests) that they did this, probably because it wasn't worth bloating structures that may not ever be optimized with MOVW usages. I have no idea what sort of answer would satisfy this question.
    – timemage
    Sep 5, 2022 at 19:09
  • OK, how about if I simplify it a bit, and we're talking about a class whose only member is a uint8_t[3]. Basically, I want to make sure that if I bend over backwards & do my own complicated bit-packing to make everything fit into 3 bytes, the compiler isn't going to just laugh at me and make it take 4 bytes anyway.
    – Bitbang3r
    Sep 5, 2022 at 20:32
  • It kind of sounds like you're heading in the direction of something like a GCC extension __attribute__ ((__packed__)) or the #pragma or C++ attribute way (way; it's still non-standard) of specifying it, but really I don't know. I expect this attribute does nothing in avr-gcc because it would seem to be the normal behaviour in that compiler under common options anyway. But again... I don't know.
    – timemage
    Sep 5, 2022 at 22:43

1 Answer 1

1

GCC has an extension to add attributes to types, which you can use to tell it your alignment requirements.

Additionally it has a #pragma to define the alignment, which could be used alternatively.

But as you already suspect, for this 8-bit target there should be no reason to insert padding bytes.


So your question boils down to find a method to detect unwanted padding. You can do this in a standard compliant way, which I show below. Yes, it does introduce additional types, one per structure. But it does not generate any unnecessary objects that eat your precious resources.

The "trick" is to use the sizeof operator to calculate a valid array size only, if the size of the checked type meets the expectation. If not, a compiler error safely breaks the generation of code. Granted, the error message is misleading an unsuspecting user, so it is really necessary to name the macro and the type accordingly.

This example compiles successfully with a PC as target, but not with an AVR as target. This is intentionally to demonstrate the working, because with an AVR target the type UP should have no padding bytes.

#define ASSERT_TYPE_SIZE(Type, size) \
    typedef char Assert##Type##Size[2 * (sizeof(Type) == (size)) - 1]

typedef struct {
    char c;
    long l;
} UP;
ASSERT_TYPE_SIZE(UP, sizeof (long) + sizeof(long));

typedef struct __attribute__((packed)) {
    char c;
    long l;
} P1;
ASSERT_TYPE_SIZE(P1, sizeof (char) + sizeof(long));

#pragma pack(1)
typedef struct {
    char c;
    long l;
} P2;
ASSERT_TYPE_SIZE(P2, sizeof (char) + sizeof(long));
#pragma pack()

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.