8.2. Data types

In many programming environments for C and C-derived languages on 64-bit machines, int variables are still 32 bits wide, but long integers and pointers are 64 bits wide. These are described as having an LP64 data model. This chapter assumes LP64, though other data models are available, see Table 5.1.

The ARM ABI defines a number of basic data types for LP64. Some of these can vary between architectures, and are included in the following:

Table 8.1. Basic data types

long long64-bit64-bitinteger
float32-bit32-bitsingle-precision IEEE floating-point
64-bit64-bitdouble-precision IEEE floating-point

16-bit unsigned

16-bit unsigned

short (compiler dependent)

32-bit unsigned

32-bit unsigned

int (compiler dependent)

void* pointer32-bit64-bitaddresses to data or code
enumerated types32-bit32-bit[b]signed or unsigned integer
bit fieldsnot larger than their natural container size
ABI defined extension types
__int128/__uint128128-bit128-bitsigned/unsigned quadword
__f1616-bit16-bithalf precision

[a] Environment-dependent. In GNU-based systems (such as Linux) this type is always 32-bit.

[b] If the set of values in an enumerated type cannot be represented using either int or unsigned int as a container type, and the language permits extended enumeration sets, then a long long or unsigned long long container may be used.

When comparing AArch64 with previous versions of the ARM architecture, 64-bit data types can typically be handled more efficiently, because of 64-bit general-purpose registers and operations. An int is still 32-bit, which can be handled efficiently through the available 32-bit view of the general-purpose registers (W registers). Pointers, however, are 64-bit addresses to data or code. The ARM ABI defines char to be unsigned by default. This is also true for previous versions of the architecture.

Porting is simplified if your code does not manipulate pointers in non-portable ways, such as cases of casting to or from non-pointer types or performing pointer arithmetic. This means you have never stored a pointer in an int variable (with the possible exception of intptr_t and uintptr_t) and have never cast a pointer to an int. For more information on this, see Issues when porting code from a 32-bit to 64-bit environment.

Among other effects, this changes the size, and possibly the alignment of structures and parameter lists. Use the int32_t and int64_t types from stdint.h in cases where storage size matters. Note that size_t and ssize_t are both 64 bit in AAPCS64-LP64.

For performance reasons, the compiler tries to align data on natural size boundaries. Most compilers try to optimize the layout of global data within a compilation module.

AArch64 provides support for 16, 32, 64 and 128-bit data unaligned accesses, where the address used is not a multiple of the quantity to be loaded or stored. However, exclusive load or store and load-acquire or store-release instructions can only access aligned addresses. This means that variables used to construct semaphores and other locking mechanisms must typically be aligned.


Under normal circumstances all variables should be aligned. Unaligned access are still less efficient on average than aligned access in most cases.

Unaligned accesses are never guaranteed to be atomic with respect to other CPUs or bus masters in the system.

The only major exception to this rule is access to packed data structures -- this can save significant effort when marshaling data to/from the outside world, via files or network connection etc.

Unaligned accesses might have a performance impact when compared with aligned accesses. Data aligned on a natural size boundary is accessed more efficiently and unaligned accesses might cost additional bus or cache cycles. The packed attribute ( __attribute__((packed, aligned(1))) should be used to warn the compiler of potential unaligned accesses, for example when manually casting pointers pointing to different data types.

Copyright © 2015 ARM. All rights reserved.ARM DEN0024A