Search code examples
c++gccclangstdoption-type

GCC problem with std::optional and packed struct member


I need to use a packed struct for parsing incoming data. I also have an std::optional value that I want to assign the value of one of the struct members. However, it fails. I think I understand the problem, basically making a reference to a variable that is not aligned with the memory width can be a bad thing.

The code example compiles with Clang 14, but not with GCC 12. Is it a bug or a "feature"?

#include <cstdint>
#include <optional>

struct Data {
    uint8_t a{};
    uint32_t b{};
} __attribute__((packed));

int main() {
    Data t;
    std::optional<uint32_t> val{};
    val = t.b;  // <<< This fails
    val = (decltype(t.b)) t.b;
}

Clang: https://godbolt.org/z/eWfaqb3a3

GCC: https://godbolt.org/z/or1W5MbdG

I know of the general problems with packed structs. As my target is an embedded device with a x86-64 and the data being parsed comes from an industrial standard bus, I believe I'm safe from that.


Solution

  • This is a problem of how Clang and GCC assume the ARM CPU is configured.

    ARM CPUs have a bit that says whether unaligned access should cause a processor trap or be handled by the CPU using slower access methods transparently. Clang defaults to the CPU allowing unaligned access while GCC defaults to the CPU trapping unaligned access.

    So for Clang, it is perfectly fine to create a int& for the unaligned t.b, because the CPU will handle the unaligned access that might cause transparently.

    For GCC on the other hand, creating an int& from t.b risk code accessing it and causing a trap. The contract for functions taking an int& says the int must be aligned. So the compiler fails because the contract can't be satisfied.

    But if you write (decltype(t.b)) t.b; you create a copy of t.b to be used which then avoids the unaligned problem because the compiler knows how to copy an unaligned uint32_t.

    You can specify compiler flags to change the default assumption about unaligned access. Allowing it for GCC should make the code compile, but it assumes your ARM CPU will be configure to allow said unaligned access.