Structures, unions, enumerations, and bitfields in ARM C and C++
Non-Confidential | ARM DUI0375E | |||
|
||||
Home > C and C++ Implementation Details > Structures, unions, enumerations, and bitfields in ARM C and C++ |
10.4 Structures, unions, enumerations, and bitfields in ARM C and C++
Describes the implementation of the structured data types union
, enum
, and struct
. It also discusses structure padding and bitfield implementation.
Unions
When a member of a
union
is accessed using a member of a different type, the resulting value can be predicted from the representation of the original type. No error is given.Enumerations
An object of type
enum
is implemented in the smallest integral type that contains the range of the enum
.In C mode, and in C++ mode without
--enum_is_int
, if an enum
contains only positive enumerator values, the storage type of the enum
is the first unsigned type from the following list, according to the range of the enumerators in the enum
. In other modes, and in cases where an enum
contains any negative enumerator values, the storage type of the enum
is the first of the following, according to the range of the enumerators in the enum
:-
unsigned char
if not using--enum_is_int
-
signed char
if not using--enum_is_int
-
unsigned short
if not using--enum_is_int
-
signed short
if not using--enum_is_int
-
signed int
-
unsigned int
except C with--strict
-
signed long long
except C with--strict
-
unsigned long long
except C with--strict
.
Note
In ARM Compiler 4.1 and later, the storage type of theenum
being the first unsigned type from the list applies irrespective of mode.Implementing
enum
in this way can reduce data size. The command-line option --enum_is_int
forces the underlying type of enum
to at least as wide as int
.See the description of C language mappings in the Procedure Call Standard for the ARM Architecture specification for more information.
Note
Care must be taken when mixing translation units that have been compiled with and without the
--enum_is_int
option, and that share interfaces or data structures.In strict C, enumerator values must be representable as
int
s. That is, they must be in the range -2147483648 to +2147483647, inclusive. A warning is issued for out-of-range enumerator values:#66: enumeration value is out of "int" range
Such values are treated the same way as in C++, that is, they are treated as
unsigned int
, long long
, or unsigned long long
.To ensure that out-of-range Warnings are reported, use the following command to change them into Errors:
armcc --diag_error=66 ...
Structures
The following points apply to:
-
all C structures
-
all C++ structures and classes not using virtual functions or base classes.
Structures can contain padding to ensure that fields are correctly aligned and that the structure itself is correctly aligned. The following diagram shows an example of a conventional, nonpacked structure. Bytes 1, 2, and 3 are padded to ensure correct field alignment. Bytes 11 and 12 are padded to ensure correct structure alignment. The
sizeof()
function returns the size of the structure including padding.Figure 10-1 Conventional nonpacked structure example
The compiler pads structures in one of the following ways,
according to how the structure is defined:
-
Structures that are defined as
static
orextern
are padded with zeros. -
Structures on the stack or heap, such as those defined with
malloc()
orauto
, are padded with whatever is previously stored in those memory locations. You cannot usememcmp()
to compare padded structures defined in this way.
Use the
--remarks
option to view the messages that are
generated when the compiler inserts padding in a struct
.Structures with empty initializers are permitted in C++:
struct { int x; } X = { };
However, if you are compiling C, or compiling C++ with the
--cpp
and--c90
options, an error is generated.Packed structures
A packed structure is one where the alignment of the structure, and of the fields within it, is always 1.
You can pack specific structures with the
__packed
qualifier. Alternatively, you can use #pragma pack(n
)
to make sure that any structures with unaligned data are packed. There is no command-line option to change the default packing of structures.Bitfields
In nonpacked structures, the ARM compiler allocates bitfields in containers. A container is a correctly aligned object of a declared type.
Bitfields are allocated so that the first field specified occupies the lowest-addressed bits of the word, depending on configuration:
- Little-endian
-
Lowest addressed means least significant.
- Big-endian
-
Lowest addressed means most significant.
A bitfield container can be any of the integral types.
Note
In strict 1990 ISO Standard C, the only types permitted for a bit field are
int
, signed int
, and unsigned int
. For non-int
bitfields, the compiler displays an error.A plain bitfield, declared without either
signed
or unsigned
qualifiers, is treated as unsigned
. For example, int x:10
allocates an unsigned integer of 10 bits.A bitfield is allocated to the first container of the correct type that has a sufficient number of unallocated bits, for example:
struct X { int x:10; int y:20; };
The first declaration creates an integer container and allocates 10 bits to
x
. At the second declaration, the compiler finds the existing integer container with a sufficient number of unallocated bits, and allocates y
in the same container as x
.A bitfield is wholly contained within its container. A bitfield that does not fit in a container is placed in the next container of the same type. For example, the declaration of
z
overflows the container if an additional bitfield is declared for the structure:struct X { int x:10; int y:20; int z:5; };
The compiler pads the remaining two bits for the first container and assigns a new integer container for
z
.Bitfield containers can overlap each other, for example:
struct X { int x:10; char y:2; };
The first declaration creates an integer container and allocates 10 bits to
x
. These 10 bits occupy the first byte and two bits of the second byte of the integer container. At the second declaration, the compiler checks for a container of type char
. There is no suitable container, so the compiler allocates a new correctly aligned char
container.Because the natural alignment of
char
is 1, the compiler searches for the first byte that contains a sufficient number of unallocated bits to completely contain the bitfield. In the example structure, the second byte of the int
container has two bits allocated to x
, and six bits unallocated. The compiler allocates a char
container starting at the second byte of the previous int
container, skips the first two bits that are allocated to x
, and allocates two bits to y
.If
y
is declared char y:8
, the compiler pads the second byte and allocates a new char
container to the third byte, because the bitfield cannot overflow its container. The following figure shows the bitfield allocation for the following example structure:struct X { int x:10; char y:8; };
Figure 10-2 Bitfield allocation 1
Note
The same basic rules apply to bitfield declarations with different
container types. For example, adding an
int
bitfield to the example structure gives:struct X { int x:10; char y:8; int z:5; }
The compiler allocates an
int
container starting at the same location as the int x:10
container and allocates a byte-aligned char
and 5-bit bitfield, as follows:Figure 10-3 Bitfield allocation 2
You can explicitly pad a bitfield container by declaring an unnamed
bitfield of size zero. A bitfield of zero size fills the container up to the end
if the container is not empty. A subsequent bitfield declaration starts a new
empty container.
Note
As an optimization, the compiler might overwrite padding bits in a
container with unspecified values when a bitfield is written. This does not
affect normal usage of bitfields.
Bitfields in packed structures
Packed bitfield containers, including all bitfield containers in
packed structures, have an alignment of 1. Therefore the maximum bit padding
inserted to align a packed bitfield container is 7 bits.
For an unpacked bitfield container, the maximum bit padding is
8*sizeof(container-type)-1
bits.Tail-padding is always inserted into the structure as necessary to
ensure arrays of the structure will have their elements correctly aligned.
A packed bitfield container is only large enough (in bytes) to hold
the bitfield that declared it. Non-packed bitfield containers are the size of
their type.
The following examples illustrate these interactions.
struct A { int z:17; }; // sizeof(A) = 4, alignment = 4 struct A { __packed int z:17; }; // sizeof(A) = 3, alignment = 1 __packed struct A { int z:17; }; // sizeof(A) = 3, alignment = 1
struct A { char y:1; int z:31; }; // sizeof(A) = 4, alignment = 4 struct A { char y:1; __packed int z:31; }; // sizeof(A) = 4, alignment = 1 __packed struct A { char y:1; int z:31; }; // sizeof(A) = 4, alignment = 1
struct A { char y:1; int z:32; }; // sizeof(A) = 8, alignment = 4 struct A { char y:1; __packed int z:32; }; // sizeof(A) = 5, alignment = 1 __packed struct A { char y:1; int z:32; }; // sizeof(A) = 5, alignment = 1
struct A { int x; char y:1; int z:31; }; // sizeof(A) = 8, alignment = 4 struct A { int x; char y:1; __packed int z:31; }; // sizeof(A) = 8, alignment = 4 __packed struct A { int x; char y:1; int z:31; }; // sizeof(A) = 8, alignment = 1
struct A { int x; char y:1; int z:32; }; // sizeof(A) = 12, alignment = 4 [1] struct A { int x; char y:1; __packed int z:32; }; // sizeof(A) = 12, alignment = 4 [2] __packed struct A { int x; char y:1; int z:32; }; // sizeof(A) = 9, alignment = 1
Note that [1] and [2] are not identical; the location of z within the structure and the tail-padding differ.
struct example1 { int a : 8; /* 4-byte container at offset 0 */ __packed int b : 8; /* 1-byte container at offset 1 */ __packed int c : 24; /* 3-byte container at offset 2 */ }; /* Total size 8 (3 bytes tail padding) */;
struct example2 { __packed int a : 8; /* 1-byte container at offset 0 */ __packed int b : 8; /* 1-byte container at offset 1 */ int c : 8; /* 4-byte container at offset 0 */ }; /* Total size 4 (No tail padding) */
struct example3 { int a : 8; /* 4-byte container at offset 0 */ __packed int b : 32; /* 4-byte container at offset 1 */ __packed int c : 32; /* 4-byte container at offset 5 */ int d : 16; /* 4-byte container at offset 8 */ int e : 16; /* 4-byte container at offset 12 */ int f : 16; /* In previous container */ }; /* Total size 16 (No tail padding) */
struct X { int x:10; char y:8; int z:5; }