As initially reported by Michal on https://answers.launchpad.net/gcc-arm-embedded/+question/345145 gcc seems to be showing some weird behavior when it comes to passing a reference to a member in a packed structure. I was able to reduce the testcase presented in that launchpad ticket to the following program: $cat t.cpp: #define PACKED __attribute__ ((packed)) #define TYPE_C short typedef struct { TYPE_C c; } PACKED test_struct; class A { const TYPE_C &c; public: A (const TYPE_C & _c) : c(_c) {}; }; class B { public: B(); A foo (); private: test_struct * s; }; A B::foo () { return A (s->c); } Compiling this with $arm-none-eabi-g++ -mcpu=cortex-m7 -mthumb -S -O1 t.cpp -fdump-tree-optimized Will yield the following dump: ;; Function A B::foo() (_ZN1B3fooEv, funcdef_no=3, decl_uid=4607, cgraph_uid=3, symbol_order=3) A B::foo() (struct B * const this) { const short int D.4636; struct A D.4650; <bb 2>: MEM[(struct A *)&D.4650] = &D.4636; D.4636 ={v} {CLOBBER}; return D.4650; } As you can see, it will not load the struct's field. Changing the 'TYPE_C' define to 'char' will yield the following dump: ;; Function A B::foo() (_ZN1B3fooEv, funcdef_no=3, decl_uid=4607, cgraph_uid=3, symbol_order=3) A B::foo() (struct B * const this) { struct A D.4649; struct test_struct * _1; char * _2; <bb 2>: _1 = this_4(D)->s; _2 = &_1->c; MEM[(struct A *)&D.4649] = _2; return D.4649; } Now when the type is 'char' it seems to be able to get the fields address. Can anyone shine some light on this for me? Is referencing a packed structure's member that is not guaranteed to be aligned (so not char) undefined behavior?
Nicely narrows Andre, Thanks. Added myself to CC to track progress.
This happens on x86_64 as well and is because you invoke undefined behavior when extending the lifetime of A::c across the lifetime of the object it refers to. s->c is an rvalue here because it's type isn't compatible due to the mismatched alignment and thus a temporary of correct alignment is build and the address of that is passed to A::A. I suppose the FE could emit a warning here. Oh, and the .optimized you see is because the FE emits a CLOBBER to explicitely end the lifetime of said temporary: A B::foo() (struct B * const this) { const short int & SR.1; const short int D.2330; struct A D.2331; struct A D.2342; struct test_struct * _1; short int _2; <bb 2>: _1 = this_4(D)->s; _2 = _1->c; D.2330 = _2; MEM[(struct A *)&D.2342] = &D.2330; D.2330 ={v} {CLOBBER}; return D.2342; thus we can DSE the store to D.2330.
Thank you Richard! I have a follow up question. Why is this only a problem when passing by reference and not when passing a pointer? So say: #define PACKED __attribute__ ((packed)) #define TYPE_C short typedef struct { TYPE_C c; } PACKED test_struct; class A { const TYPE_C * c; public: A (const TYPE_C * _c) : c(_c) {}; }; class B { public: B(); A foo (); private: test_struct * s; }; A B::foo () { return A (&(s->c)); } Wouldn't there still be an alignment mismatch between A::c and s->c?
On Fri, 26 Aug 2016, andre.simoesdiasvieira at arm dot com wrote: > https://gcc.gnu.org/bugzilla/show_bug.cgi?id=77388 > > --- Comment #3 from Andre Vieira <andre.simoesdiasvieira at arm dot com> --- > Thank you Richard! > > I have a follow up question. Why is this only a problem when passing by > reference and not when passing a pointer? > > So say: > #define PACKED __attribute__ ((packed)) > > #define TYPE_C short > > typedef struct { > TYPE_C c; > } PACKED test_struct; > > class A > { > const TYPE_C * c; > public: > A (const TYPE_C * _c) : > c(_c) {}; > }; > > class B > { > public: > B(); > A foo (); > private: > test_struct * s; > }; > > A B::foo () > { > return A (&(s->c)); > } > > Wouldn't there still be an alignment mismatch between A::c and s->c? Yes, this still invokes undefined behavior. You just don't get bitten by the C++ FE choosing to use a temporary for the const reference. I suppose using a non-const reference would "work" as well. Note the above is also not diagnosed.
I see, thank you! Oh and leaving out the const yields an error: t.cpp:28:16: error: cannot bind packed field '((B*)this)->B::s->test_struct::c' to 'short int&' return A (s->c);
On Fri, 26 Aug 2016, andre.simoesdiasvieira at arm dot com wrote: > https://gcc.gnu.org/bugzilla/show_bug.cgi?id=77388 > > --- Comment #5 from Andre Vieira <andre.simoesdiasvieira at arm dot com> --- > I see, thank you! > > Oh and leaving out the const yields an error: > t.cpp:28:16: error: cannot bind packed field '((B*)this)->B::s->test_struct::c' > to 'short int&' > return A (s->c); Ah, great!