Home > Back-end >  GCC is complaining about using multiple __attributes__
GCC is complaining about using multiple __attributes__

Time:11-01

I've got some code provided by a vendor that I'm using and its typedef'ing an enum with __attribute__((aligned(1), packed)) and GCC is complaining about the multiple attributes:

error: ignoring attribute 'packed' because it conflicts with attribute 'aligned' [-Werror=attributes]

Not sure what the best approach is here. I feel like both of these attributes are not necessary. Would aligned(1) not also make it packed? And is this even necessary for an enum? Wouldn't it be best to have the struct that this enum goes into be packed?

Thanks!

I've removed the packed attribute and that works to make GCC happy but I want to make sure that it will still behave the same. This is going into an embedded system that relies on memory mapped registers so I need the mappings to be correct or else things won't work.

Here's an example from the code supplied by the vendor:

#define DMESCC_PACKED        __attribute__ ((__packed__))
#define DMESCC_ENUM8         __attribute__ ((aligned (1), packed))

typedef enum DMESCC_ENUM8 {DMESCC_OFF, DMESCC_ON} dmescc_bittype_t;

typedef volatile struct {
  dmescc_bittype_t rx_char_avail  : 1;
  dmescc_bittype_t zero_count     : 1;
  dmescc_bittype_t tx_buf_empty   : 1;
  dmescc_bittype_t dcd            : 1;
  dmescc_bittype_t sync_hunt      : 1;
  dmescc_bittype_t cts            : 1;
  dmescc_bittype_t txunderrun_eom : 1;
  dmescc_bittype_t break_abort    : 1;
} DMESCC_PACKED dmescc_rr0_t;

When I build the above code I get the GCC error I mentioned above.

CodePudding user response:

Documentation here: https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html#Common-Variable-Attributes emphasis mine:

When used on a struct, or struct member, the aligned attribute can only increase the alignment; in order to decrease it, the packed attribute must be specified as well. When used as part of a typedef, the aligned attribute can both increase and decrease alignment, and specifying the packed attribute generates a warning.

Note that the effectiveness of aligned attributes for static variables may be limited by inherent limitations in the system linker and/or object file format. On some systems, the linker is only able to arrange for variables to be aligned up to a certain maximum alignment. (For some linkers, the maximum supported alignment may be very very small.) If your linker is only able to align variables up to a maximum of 8-byte alignment, then specifying aligned(16) in an __attribute__ still only provides you with 8-byte alignment. See your linker documentation for further information.

Older GNU documentation said something else. Also, I don't know what the documentation is trying to say here: "specifying the packed attribute generates a warning", because there is no warning in case I do this (gcc x86_64 12.2.0 -Wall -Wextra):

typedef struct
{
  char ch;
  int   i;
} __attribute__((aligned(1), packed)) aligned_packed_t;

However, this effectively places the struct on a 5 byte offset, where the first address appears to be 8 byte aligned (which might be a thing of the linker as suggested in the above docs). We'd have to place it in an array to learn more.

Since I don't really don't trust the GNU documentation, I did some trial & error to reveal how these work in practice. I created 4 structs:

  • one with aligned(1)
  • one with such a struct as its member and also aligned(1) in itself
  • one with packed
  • one with both aligned(1) and packed (again this compiles cleanly no warnings)

For each struct I created an array, then printed the address of the first 2 array items. Example:

#include <stdio.h>

typedef struct
{
  char ch;
  int   i;
} __attribute__((aligned(1))) aligned_t;

typedef struct
{
  char ch;
  aligned_t aligned_member;
} __attribute__((aligned(1))) struct_aligned_t;

typedef struct
{
  char ch;
  int   i;
} __attribute__((packed)) packed_t;

typedef struct
{
  char ch;
  int   i;
} __attribute__((aligned(1),packed)) aligned_packed_t;

#define TEST(type,arr) \
printf("%-16s  Address: %p  Size: %zu\n", #type, (void*)&arr[0], sizeof(type)); \
printf("%-16s  Address: %p  Size: %zu\n", #type, (void*)&arr[1], sizeof(type));

int main (void)
{
  aligned_t        arr1 [3];
  struct_aligned_t arr2 [3];
  packed_t         arr3 [3];
  aligned_packed_t arr4 [3];

  TEST(aligned_t, arr1);
  TEST(struct_aligned_t, arr2);
  printf("  Address of member: %p\n", arr2[0].aligned_member);
  TEST(packed_t, arr3);
  TEST(aligned_packed_t, arr4);
}

Output on x86 Linux:

aligned_t         Address: 0x7ffc6f3efb90  Size: 8
aligned_t         Address: 0x7ffc6f3efb98  Size: 8
struct_aligned_t  Address: 0x7ffc6f3efbb0  Size: 12
struct_aligned_t  Address: 0x7ffc6f3efbbc  Size: 12
  Address of member: 0x40123000007fd8
packed_t          Address: 0x7ffc6f3efb72  Size: 5
packed_t          Address: 0x7ffc6f3efb77  Size: 5
aligned_packed_t  Address: 0x7ffc6f3efb81  Size: 5
aligned_packed_t  Address: 0x7ffc6f3efb86  Size: 5
  • The first struct with just aligned(1) didn't make any difference against a normal struct.
  • The second struct where the first struct was included as a member, to see if it would be misaligned internally, did not pack it any tighter either, nor did the member get allocated at a misaligned (1 byte) address.
  • The third struct with only packed did get allocated at a potentially misaligned address and packed into 5 bytes.
  • The fourth struct with both aligned(1) and packed works just as the one that had packed only.

So my conclusion is that "the aligned attribute can only increase the alignment" is correct and as expected aligned(1) is therefore nonsense. However, you can use it to increase the alignment. ((aligned(16), packed) did give 16 bit size, which effectively cancels packed.

Also I can't make sense of this part of the manual:

When used as part of a typedef, the aligned attribute can both increase and decrease alignment, and specifying the packed attribute generates a warning.

Either I'm missing something or the docs are wrong (again)...

CodePudding user response:

Not sure what the best approach is here. I feel like both of these attributes are not necessary. Would aligned(1) not also make it packed?

No, it wouldn't. From the docs:

The aligned attribute specifies a minimum alignment (in bytes) for variables of the specified type.

and

When attached to an enum definition, the packed attribute indicates that the smallest integral type should be used.

These properties are related but neither is redundant with the other (which makes GCC's diagnostic surprising).

And is this even necessary for an enum? Wouldn't it be best to have the struct that this enum goes into be packed?

It is meaningful for an enum to be packed regardless of how it is used to compose other types. In particular, having packed on an enum is (only) about the storage size of objects of the enum type. It does not imply packing of structure types that have members of the enum type, but you might want that, too.

On the other hand, the alignment requirement of the enum type is irrelevant to the layout of structure types that have the packed attribute. That's pretty much the point of structure packing.

I've removed the packed attribute and that works to make GCC happy but I want to make sure that it will still behave the same. This is going into an embedded system that relies on memory mapped registers so I need the mappings to be correct or else things won't work.

If only one of the two attributes can be retained, then packed should be that one. Removing it very likely does cause meaningful changes, especially if the enum is used as a structure member or as the type of a memory-mapped register. I can't guarantee that removing the aligned attribute won't also cause behavioral changes, but that's less likely.

It might be worth your while to ask the vendor what version of GCC they use for development and testing, and what range of versions they claim their code is compatible with.

Overall, however, the whole thing has bad code smell. Where it is essential to control storage size exactly, explicit-size integer types such as uint8_t should be used.

Addendum

With regard to the example code added to the question: that the enum type in question is used as the type of a bitfield changes the complexity of the question. Portable code steers well clear of bitfields.

The C language specification does not guarantee that an enum type such as that one can be used as the type of a bitfield member, so you're straight away into implementation-defined territory. Not that using one of the types the specification designates are supported would delay that very long, because many of the properties of bitfields and the structures containing them are implementation defined anyway, in particular,

  • which data types other than qualified and unqualified versions of _Bool, signed int, and unsigned int are allowed as the type of a bitfield member;
  • the size and alignment requirement of the addressible storage units in which bitfields are stored (the spec does not connect these in any way with bitfields' declared types);
  • whether bitfields assigned to the same addressible storage unit are arranged from least-significant position to most, or the opposite;
  • whether bitfields can be split across adjacent addressible storage units;
  • whether bitfield members may have atomic type.

GCC's definitions for these behaviors are here: https://gcc.gnu.org/onlinedocs/gcc/Structures-unions-enumerations-and-bit-fields-implementation.html#Structures-unions-enumerations-and-bit-fields-implementation. Note well that many of them depend on the target ABI.

To use the vendor code safely, you really need to know which compiler it was developed for and tested against, and if that's a version of GCC, what target ABI. If you learn or are willing to assume GCC targeting the same ABI that you are targeting, then keep packed, dump aligned(1), and test thoroughly. Otherwise, you'll probably want to do more research.

  • Related