3.1.3. Variable declaration keywords

This section describes the implementation of various standard and ARM-specific variable declaration keywords. Standard C or C++ keywords that do not have ARM-specific behavior or restrictions are not documented. See also Type qualifiers for information on qualifiers such as volatile and __packed

Standard keywords

These keywords declare a storage class.


Using the ARM compilers, you can declare any number of local objects (auto variables) to have the storage class register.


Using register is not recommended because the compiler is very effective at optimizing code. The register keyword is regarded by the compiler as a suggestion only. Other variables, not declared with the register keyword, can be kept in registers and register variables can be kept in memory. Using register might increase code size because the compiler is restricted in its use of registers for optimization.

Depending on the variant of the ATPCS being used, there are between five and seven integer registers available, and four floating-point registers. In general, declaring more than four integer register variables and two floating-point register variables is not recommended.

The following object types can be declared to have the register storage class:

  • All integer types (long long occupies two registers).

  • All integer-like structures. That is, any one word struct or union where all addressable fields have the same address, or any one word structure containing bitfields only. The structure must be padded to 32 bits.

  • Any pointer type.

  • Floating-point types. The double-precision floating-point type double occupies two ARM registers if software floating-point is used.

ARM-specific keywords

The keywords in this section are used to declare or modify variable definitions:


This type specifier is an alternative name for type long long. This is accepted even when using -strict.


This storage class allocates the declared variable to a global integer register variable. If you use this storage class, you cannot also use any of the other storage classes such as extern, static, or typedef. vreg is an ATPCS callee-save register (for example, v1) and not a real register number (for example, r4). In C, global register variables cannot be qualified or initialized at declaration. In C++, any initialization is treated as a dynamic initialization. Valid types are:

  • any integer type, except long long

  • any pointer type.

For example, to declare a global integer register variable allocated to r5 (the ATPCS register v2), use the following:

__global_reg(2) int x;

The global register must be specified in all declarations of the same variable. For example, the following is an error:

int x;
__global_reg(1) int x; // error

Also, __global_reg variables in C cannot be initialized at definition. For example, the following is an error in C, though not in C++:

__global_reg(1) int x=1; // error in C

Depending on the ATPCS variant used, between five and seven integer registers, and four floating-point registers are available for use as global register variables. In practice, using more than three global integer register variables in ARM code, or one global integer register variable in Thumb code, or more than two global floating-point register variables is not recommended.


In Thumb, __global_reg(4) is not allowed.

Unlike register variables declared with the standard register keyword, the compiler does not move global register variables to memory as required. If you declare too many global variables, code size increases significantly. In some cases, your program might not compile.


You must take care when using global register variables because:

  • There is no check at link time to ensure that direct calls between different compilation units are sensible. If possible, define global register variables used in a program in each compilation unit of the program. In general, it is best to place the definition in a global header file. You must set up the value in the global register early in your code, before the register is used.

  • A global register variable maps to a callee-saved register, so its value is saved and restored across a call to a function in a compilation unit that does not use it as a global register variable, such as a library function.

  • Calls back into a compilation unit that uses a global register variable are dangerous. For example, if a global register using function is called from a compilation unit that does not declare the global register variable, the function reads the wrong values from its supposed global register variables.

  • This class can only be used at file scope.

Type qualifiers

This section describes the implementation of various standard C and ARM-specific type qualifiers. These type qualifiers can be used to instruct the compiler to treat the qualified type in a special way. Standard qualifiers that do not have ARM-specific behavior or restrictions are not documented.


The __align() storage class modifier aligns a top-level object on an byte boundary. 8-byte alignment is required if you are using the LDRD or STRD instructions, and can give a significant performance advantage with VFP instructions.

For example, if you are using LDRD or STRD instructions to access data objects defined in C or C++ from ARM assembly language, you must use the __align(8) storage class specifier to ensure that the data objects are properly aligned.

You can specify a power of 2 for the alignment boundary, however 8 is the maximum for auto variables. You can only overalign. That is you can make a 2-byte object 4-byte aligned, but you cannot align a 4-byte object at 2 bytes.

__align(8) is a storage class modifier. This means that it can be used only on top-level objects. You cannot use it on:

  • types, including typedefs and structure definitions

  • function parameters.

It can be used in conjunction with extern and static.

__align(8) only ensures that the qualified object is 8-byte aligned. This means, for example, that you must explicitly pad structures if required.


The ARM-Thumb Procedure Call Standard requires that the stack is 8-byte aligned at all external interfaces. The ARM compilers and C libraries ensure that 8-byte alignment of the stack is maintained. In addition, the default C library memory model maintains 8-byte alignment of the heap.


The __packed qualifier sets the alignment of any valid type to 1. This means:

  • there is no padding inserted to align the packed object

  • objects of packed type are read or written using unaligned accesses.

The __packed qualifier cannot be used on:

  • floating-point types

  • structures or unions with floating-point fields

  • structures that were previously declared without __packed.


__packed is not, strictly speaking, a type qualifier. It is included in this section because it behaves like a type qualifier in most respects.

The __packed qualifier does not affect local variables of integral type.

The __packed qualifier applies to all members of a structure or union when it is declared using __packed. There is no padding between members, or at the end of the structure. All substructures of a packed structure must be declared using __packed. Integral subfields of an unpacked structure can be packed individually.

A packed structure or union is not assignment-compatible with the corresponding unpacked structure. Because the structures have a different memory layout, the only way to assign a packed structure to an unpacked structure is by a field-by-field copy.

The effect of casting away __packed is undefined. The effect of casting a nonpacked structure to a packed structure is undefined. A pointer to an integral type can be legally cast, explicitly or implicitly, to a pointer to a packed integral type.

A pointer can point to a packed type (Example 3.2).

Example 3.2. Pointer to packed

__packed int *p

There are no packed array types. A packed array is an array of objects of packed type. There is no padding in the array.


On ARM processors, access to unaligned data can take up to seven instructions and three work registers. Data accesses through packed structures must be minimized to avoid increase in code size, and performance loss.

The __packed qualifier is useful to map a structure to an external data structure, or for accessing unaligned data, but it is generally not useful to save data size because of the relatively high cost of access. The number of unaligned accesses can be reduced by only packing fields in a structure that requires packing.

When a packed object is accessed using a pointer, the compiler generates code that works and that is independent of the pointer alignment (Example 3.3).

Example 3.3. Packed structure

typedef __packed struct
    char x;       // all fields inherit the __packed qualifier	
    int y;
}X;        // 5 byte structure, natural alignment = 1	
int f(X *p)
    return p->y;    // does an unaligned read
typedef struct
    short x;
    char y;
    __packed int z; // only pack this field
    char a;
}Y;    // 8 byte structure, natural alignment = 2
int g(Y *p)
    return p->z + p->x;    // only unaligned read for z

The standard ANSI qualifier volatile informs the compiler that the qualified type contains data that can be changed from outside the program. The compiler does not attempt to optimize accesses to volatile types. For example, volatile structures can be mapped onto memory-mapped peripheral registers:

/* define a memory-mapped port register */
volatile unsigned *port = (unsigned int *) 0x40000000;
/* to access the port */
*port = value     /* write to port */
value = *port     /* read from port */

In ARM C and C++, a volatile object is accessed if any word or byte (or halfword on ARM architectures with halfword support) of the object is read or written. For volatile objects, reads and writes occur as directly implied by the source code, in the order implied by the source code. The effect of accessing a volatile short is undefined for ARM architectures that do not support halfwords. Accessing volatile packed data is undefined.


This storage class specifies an extern object declaration that, if not present, does not cause the linker to fault an unresolved reference.

If the reference remains unresolved, its value is assumed to be NULL. Unresolved references, however, are not NULL if the reference is from code to a position-independent section or to a missing __weak function (Example 3.4).

Example 3.4. Non-NULL unresolved references

 __weak const int c;            // assume 'c' is not present in final link
 const int* f1() { return &c; } // '&c' will be non-NULL if  
                                //  compiled and linked /ropi
 __weak int i;                  // assume 'i' is not present in final link
 int* f2() { return &i; }       // '&i' will be non-NULL if 
                                // compiled and linked /rwpi
 __weak void f(void);           // assume 'f' is not present in final link
 typedef void (*FP)(void);
 FP g() { return f; }           // 'g' will return non-NULL if 
                                // compiled and linked /ropi

See also Function storage class modifiers.

Copyright © 1999-2001 ARM Limited. All rights reserved.ARM DUI 0067D