2

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 failes
    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.

nikolaj
  • 180
  • 1
  • 10
  • `I need to` Why do you _need_ to? Just extract the bytes from the incoming data and shift to proper positins. There is no need to use packet structs, you are choosing to use it, I say don't use it at all and be portable and write code yourself. – KamilCuk Jun 24 '22 at 08:08
  • If I had used assembler that would have been a good choice. When using a language that understand data types, I would use that rather than manually try to maintain a list if offsets into the data image. That approach is both inefficient and hard to maintain, but yes it is portable, if that had been a priority. – nikolaj Jun 24 '22 at 09:07

1 Answers1

3

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 assumes your ARM cpu will be configure to allow said unaligned access.

Goswin von Brederlow
  • 11,875
  • 2
  • 24
  • 42
  • Good answer. Thanks, it makes sense. I was looking for these compiler flag you mention. As I understand the ones I have read about, they change how the entire program align data. Is it possible to use "normal" alignment for the program and then just allow unaligned access to these packed structs? – nikolaj Jun 24 '22 at 09:13
  • Not sure what you read about but -munaligned-access only changes what opcodes are used for unaligned access. It doesn't change any alignment or padding the compiler will use. Aligned access is still faster and will be preferred. – Goswin von Brederlow Jun 24 '22 at 11:25
  • That is also just for the ARM version. I will live with the workaround via cast. Thank you again for a quick response. – nikolaj Jun 24 '22 at 11:30
  • @nikolaj Right, and maybe doesn't help if x86_64 gives the error too. gcc might just be picky about making unaligned pointers even when the hardware has no problems with it. – Goswin von Brederlow Jun 24 '22 at 12:23