Bug 77388 - Reference to a packed structure member
Summary: Reference to a packed structure member
Status: RESOLVED INVALID
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 7.0
: P3 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2016-08-26 09:01 UTC by Andre Vieira
Modified: 2023-05-12 20:49 UTC (History)
1 user (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Andre Vieira 2016-08-26 09:01:14 UTC
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?
Comment 1 Michał Fita 2016-08-26 09:14:00 UTC
Nicely narrows Andre, Thanks. Added myself to CC to track progress.
Comment 2 Richard Biener 2016-08-26 10:21:37 UTC
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.
Comment 3 Andre Vieira 2016-08-26 10:56:21 UTC
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?
Comment 4 rguenther@suse.de 2016-08-26 10:59:19 UTC
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.
Comment 5 Andre Vieira 2016-08-26 12:31:10 UTC
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);
Comment 6 rguenther@suse.de 2016-08-26 12:33:23 UTC
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!