Bug 36566 - Cannot bind packed field
Summary: Cannot bind packed field
Status: UNCONFIRMED
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 4.2.1
: P3 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2008-06-18 18:28 UTC by Nevin Liber
Modified: 2023-04-27 20:14 UTC (History)
10 users (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 Nevin Liber 2008-06-18 18:28:05 UTC
If I have a packed struct and try to pass a member variable by reference to a function call, I get the "error: cannot bind packed field" message.  If I explicitly create a reference to the member variable first and pass that, things work fine.  One of these behaviors must be incorrect.

This issue also exists under gcc 4.0.1 (same host/target/build triplet as above) and gcc 4.1.2 under Linux.  Under gcc 3.3.3 (on Linux), there was no error.

Here is code which demonstrates the issue:

struct Squeeze
{
    short   s;
} __attribute__((aligned(1), packed));

void VerticallyChallenged(short&) {}

int main()
{
    Squeeze oj;
    short& pit(oj.s);
    
    VerticallyChallenged(pit);  // okay
    VerticallyChallenged(oj.s); // cannot bind packed field ‘oj.Squeeze::s’ to ‘short int&’
}
Comment 1 Andrew Pinski 2008-06-18 18:31:07 UTC
This is correct you cannot take the address of a field of a packed struct.

>    short& pit(oj.s);

This should be also an error.
Comment 2 Nevin Liber 2008-06-18 18:49:11 UTC
Why is this an error (I couldn't find anything in the documentation)?

Also, if pointers are used instead of references, should that be an error (currently that compiles just fine)?
Comment 3 Nevin Liber 2008-06-18 19:06:01 UTC
Expanding on my last comment:  which lines in the following code should fail to compile:

struct Squeeze
{
    short   s;
} __attribute__((aligned(1), packed));

void VerticallyChallenged(short*) {}
void VerticallyChallenged(short&) {}

int main()
{
    Squeeze oj;
    short& pit(oj.s);
    short* ppit(&oj.s);
    
    VerticallyChallenged(ppit);     // okay
    VerticallyChallenged(&oj.s);    // okay
    
    VerticallyChallenged(pit);      // okay
    VerticallyChallenged(oj.s);     // cannot bind packed field ‘oj.Squeeze::s’ to ‘short int&’
}

Comment 4 Gabriel M. Beddingfield 2009-10-11 04:38:27 UTC
I have the same problem with g++ 4.2.4 and 4.3.2.
Comment 5 Hei 2013-09-13 04:14:47 UTC
any update on this? I run into the same issue with gcc 4.6.x
Comment 6 Gabriel M. Beddingfield 2013-09-14 23:08:05 UTC
All assignments of obj.s to type short& and short* are incorrect, and ideally they would all result in compiler errors.

The C++ spec (C++03, Sects. 3.9, 3.9.1, 3.9.2) are very clear that T and "pointer to T" have implementation-specific alignment requirements.  If you have a "pointer to T" then you may assume that it meets the alignment requirements.  I'm sure the C spec has similar language.

In the OP's case, the following code could violate the alignment requirements:

    short& pit(obj.s); /* invalid */

For example &obj.s could have an address 0x602011 (which has 1-byte alignement).  When you assign that pointer to pit... pit is now a reference (i.e. pointer) that violates the alignment requirements of short (which usually requires 2-byte alignment).

Why is this a problem?  On x86/x86_64 it's *usually* no big deal because the CPU will gracefully handle unaligned memory access (with a performance penalty).  On less forgiving hardware platforms, trying to use `pit' will result in illegal instruction exceptions.

You can pass the reference if you change the function prototype to something like:

    typedef short un_short __attribute__((alignment(1)));
    void VerticallyChallenged(un_short&) {}

...and then call it with a cast like this:

    VerticallyChallenged((un_short&)oj.s);

[amazing, the kind of stuff you learn over the course of 4 years :-)]
Comment 7 Xiao Jia 2015-03-18 00:53:47 UTC
What is the status of this?

Adding "const" makes it compile.  Is this the intended behavior or not?


struct Squeeze
{
    short   s;
} __attribute__((aligned(1), packed));

void VerticallyChallenged(const short&) {}

int main()
{
    Squeeze oj;
    const short& pit(oj.s);
    VerticallyChallenged(pit);
    VerticallyChallenged(oj.s);
}
Comment 8 Jonathan Wakely 2015-03-18 01:55:54 UTC
(In reply to Xiao Jia from comment #7)
> Adding "const" makes it compile.  Is this the intended behavior or not?

Yes, of course. A const-reference causes a temporary to be created, you didn't bind to the packed field:

    Squeeze oj;
    oj.s = 0;
    const short& pit(oj.s);
    oj.s = 1;
    printf("%d %d", oj.s, pit);

This shows that oj.s and pit are not the same variable.
Comment 9 Marc Glisse 2017-02-12 05:06:12 UTC
I was thinking that
struct __attribute__((packed)) A { int i; };
should be handled like
typedef int int_unaligned __attribute__((aligned(1)));
struct A { int_unaligned i; };
but it appears that for the aligned attribute as well we ignore the attribute when forming pointers or references. Depending on the uses, the compiler can print things like "int_unaligned* {aka int*}" or "ignoring attributes on template argument" etc. It seems to me that we should not drop the alignment-reducing attribute in those cases (it is still fine to remove it when deducing a pass-by-copy type, as in template<class T>void f(T);int_unaligned i; f(i); which could safely deduce T=int. Return types may be more tricky when they end up passed as invisible reference). The C/C++ standard only consider _Alignas/alignas to increase alignment, so I don't think changes to the case that decreases alignment would be a conformance issue.
Comment 10 Rene Rahn 2020-03-03 16:54:52 UTC
I know this is quite old now. But can someone explain me why using `#pragma pack(push, 1)` does work then? I couldn't find enough resources on that. The only thing I found is, that it does literally the same. But wouldn't then the references/pointers still not be valid?

So if I change the code to:

```
#pragma pack(push, 1)
struct Squeeze
{
    short   s;
};
#pragma pack(pop)
```

Then it works on godbolt with gcc-trunk.
Comment 11 Ed Catmur 2020-03-03 17:21:06 UTC
(In reply to rene.rahn from comment #10)
> I know this is quite old now. But can someone explain me why using `#pragma
> pack(push, 1)` does work then? 

`#pragma pack` has sharper edges. It will let you take unaligned pointers/references, but they will break on architectures that enforce alignment or under ubsan, and the compiler won't try to save you. This is for compatibility with other (older) compilers that behave similarly.

nb.

Since 4.7 `short& pit(oj.s);` gives error: cannot bind packed field 'oj.Squeeze::s' to 'short int&' (comment #1).

Since 9.1 `short* ppit(&oj.s);` and `VerticallyChallenged(&oj.s);` give warning: taking address of packed member of 'Squeeze' may result in an unaligned pointer value [-Waddress-of-packed-member] (comment #3).

And the typedef `using un_short = short __attribute__((aligned(1)));` (comment #6) gives a way to form references and pointers to these fields and pass them around. (Note that you have to cast the field access at point of use.)

So I think this can be RESOLVED FIXED version 9.1.
Comment 12 Eric Gallager 2020-03-04 18:48:00 UTC
(In reply to Rene Rahn from comment #10)
> I know this is quite old now. But can someone explain me why using `#pragma
> pack(push, 1)` does work then? I couldn't find enough resources on that. The
> only thing I found is, that it does literally the same. But wouldn't then
> the references/pointers still not be valid?
> 
> So if I change the code to:
> 
> ```
> #pragma pack(push, 1)
> struct Squeeze
> {
>     short   s;
> };
> #pragma pack(pop)
> ```
> 
> Then it works on godbolt with gcc-trunk.

There are several bugs open about inconsistencies between __attribute__((packed)) and #pragma pack; see for example: bug 93910, bug 92900, bug 68160, and bug 60972
Comment 13 Rene Rahn 2020-03-04 21:42:30 UTC
(In reply to Eric Gallager from comment #12)
> (In reply to Rene Rahn from comment #10)
> > I know this is quite old now. But can someone explain me why using `#pragma
> > pack(push, 1)` does work then? I couldn't find enough resources on that. The
> > only thing I found is, that it does literally the same. But wouldn't then
> > the references/pointers still not be valid?
> > 
> > So if I change the code to:
> > 
> > ```
> > #pragma pack(push, 1)
> > struct Squeeze
> > {
> >     short   s;
> > };
> > #pragma pack(pop)
> > ```
> > 
> > Then it works on godbolt with gcc-trunk.
> 
> There are several bugs open about inconsistencies between
> __attribute__((packed)) and #pragma pack; see for example: bug 93910, bug
> 92900, bug 68160, and bug 60972

Ah great. Thanks for pointing to this.
Comment 14 Eric Estievenart 2021-12-31 15:11:13 UTC
What about:
struct S
{
	int i;
}  __attribute__((__packed__)) __attribute((aligned(8)));

void f( S* s )
{
	int& i = s->i; // Error here
}

S::i is obviously properly aligned, the compiler should know it and accept the referencing.

As for the argument 'the field might be unaligned and this would violate... and crash...', I don´t really agree. The compiler knows the arch, the alignment,
and it can decide:
- to emit an error if unaligned(or unknown) if alignment required by arch
- to emit a warning on other arch

Note by the way that this seems accepted by clang...