| |||
| Home > ARM Compiler Reference > Compiler-specific features > Variable declaration keywords | |||
This section describes the implementation of various standard
variable declaration keywords, and those that are specific to the
ARM compiler. ISO C or C++ Standard keywords that do not have behavior
or restrictions that are specific to the ARM compiler are not documented.
See also Type qualifiers for
information on qualifiers such as volatile and __packed.
The keywords are grouped in the following sections:
These keywords declare a storage class.
Declares a local object (auto variable) to have the storage class register. A register declaration is a suggestion to the compiler that it is to attempt to put the object in a physical register. However, if no more registers are available, the compiler places the object on the stack instead.
It is recommended that you do not use register, because the compiler automatically makes the best choice. Also, if you do use register, it restricts the use of registers for optimization, and this might increase the code size.
Depending on the variant of the AAPCS being used, there are between five and seven integer registers available, and four floating-point registers. In general, it is recommended that you do not declare more than four integer register variables and two floating-point register variables.
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.
The keywords in this section are used to declare or modify variable definitions:
__int64This type specifier is an alternative name for type long
long. This is accepted even when using --strict.
__global_reg(vreg)This storage class specifier 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. is an
AAPCS callee-save register (for example, v1) and not a real register number
(for example, vregr4). 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 AAPCS 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, but not in C++:
__global_reg(1) int x=1; // error in C, OK in C++
Depending on the AAPCS variant used, between five and seven integer registers, and four floating-point registers are available for use as global register variables. In practice, it is recommended that you do not use more than three global integer register variables in ARM code, or more than one global integer register variable in Thumb code, or more than two global floating-point register variables.
In Thumb, __global_reg(4) is not valid.
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.
__align(n)The __align( storage
class modifier aligns a top-level object on an n)-byte
boundary, where n is
either 2, 4, or 8. Eight-byte alignment is required if you are using
the nLDRD 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 two for the alignment boundary, however eight is the maximum for auto variables. Also, 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() is
a storage class modifier. This means that it can be used only on
top-level objects. You cannot use it on:n
types, including typedefs and structure definitions
function parameters.
__align( can
be used in conjunction with extern and static.n)
__align(8) only ensures that the qualified
object is eight-byte aligned. This means, for example, that you
must explicitly pad structures where required.
__weakThe __weak keyword
can be applied to function and object declarations, and to function
definitions:
For declarations, this storage class specifies an extern object declaration that, if not present, does not cause the linker to fault an unresolved reference.
For example:
__weak void f(void); ... f(); // call f weakly
If the reference is made from code that compiles to a branch or branch link instruction, then either:
The
reference is resolved as branching to the next instruction. This
effectively makes the branch a NOP.
The branch is replaced by a NOP instruction.
A function or object cannot be used both weakly and nonweakly
in the same compilation. For example the following code uses f() weakly
from g() and h():
void f(void);
void g() {f();}
__weak void f(void);
void h() {f();}
It is not possible to use a function or object weakly from
the same compilation that defines the function or object. The following
code uses f() nonweakly from h():
__weak void f(void);
void h() {f();}
void f() {}
The linker does not load the function or object from a library unless
another compilation uses the function or object nonweakly. 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 (see Example 3.2).
Example 3.2. 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 the chapter on creating and using libraries in RealView Compilation Tools v2.2 Linker and Utilities Guide for details on library searching.
Functions defined with __weak export
their symbols weakly. A weakly defined function behaves like a normally
defined function unless a non-weakly defined function of the same name
is linked into the same image. If both a non-weakly defined function
and a weakly defined function exist in the same image then all calls
to the function resolve to call the non-weak function. If multiple
weak definitions are available, the linker chooses one for use by
all calls.
Weakly defined functions cannot be inlined.
Functions declared with __weak and then
defined without __weak behave as non-weak functions.
This section describes the implementation of various standard C type qualifiers and type qualifiers specific to the ARM compiler. 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 behavior or restrictions that are specific to the ARM compiler are not documented:
__packedThe __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
structures that were previously declared without __packed.
__packed is not, strictly, 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. See also Packed structures.
The effect of casting away __packed is
undefined. The effect of casting a non-packed 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 (see Example 3.3).
Example 3.3. Pointer to packed
typedef __packed int* PpI; /*pointer to a __packed int */
PpI p2; /* 'p2' has the same type as 'p' */
/* __packed is a qualifier */
/* just like 'const' or 'volatile' */
typedef int *PI; /* pointer to int */
__packed PI p3; /* a __packed pointer to a normal int */
/* -- not the same type as 'p' and 'p2' */
int *__packed p4; /* 'p4' has the same type as 'p3' */
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. Only packing fields in a structure
that requires packing can reduce the number of unaligned accesses.
When a packed object is accessed using a pointer, the compiler generates code that works and that is independent of the pointer alignment (see Example 3.4).
Example 3.4. 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 ISO qualifier volatile informs the compiler
that the qualified type contains data that can be changed from outside
the program. Such variables must be placed in RW or ZI data (not
in .constdata) so that they can be changed. This
includes const volatile variables.
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.