There are four type qualifiers:
const
volatile
__unaligned
(axp)
__restrict
(pointer type only)
Type qualifiers were introduced by the ANSI C standard to, in part,
give you greater control over the compiler's optimizations. The
const
and volatile
type qualifiers can be
applied to any type. The __restrict
type qualifier can
be applied only to pointer types.
Note that because the __restrict
type qualifier is not
part of the 1989 ANSI C standard, this keyword has double leading
underscores. The next version (9X) of the C standard is expected
to adopt the keyword restrict
with the same semantics
described in this section.
The use of const
gives you a method of controlling
write access to an object, and eliminates potential side effects
across function calls involving that object. This is because
a side effect is an alteration of an object's storage and
const
prohibits such alteration.
Use volatile
to qualify an object that can be changed
by other processes or hardware. The use of volatile
disables optimizations with respect to referencing the object. If an
object is volatile qualified, it may be changed between the time it
is initialized and any subsequent assignments. Therefore, it cannot
be optimized.
Function parameters, however, do not all share the type qualification of one parameter. For example:
int f( const int a, int b) /* a is const qualified; b is not */
When using a type qualifier with an array identifier, the elements of the array are qualified, not the array type itself.
The following declarations and expressions show the behavior when type qualifiers modify an array or structure type:
const struct s { int mem; } cs = { 1 }; struct s ncs; /* ncs is modifiable */ typedef int A[2][3]; const A a = {{4, 5, 6}, {7, 8, 9}}; /* array of array of const */ /* int's */ int *pi; const int *pci; ncs = cs; /* Valid */ cs = ncs; /* Invalid, cs is const-qualified */ pi = &ncs.mem; /* Valid */ pi = &cs.mem; /* Violates type constraints for = operator */ pci = &cs.mem; /* Valid */ pi = a[0]; /* Invalid; a[0] has type "const int *" */
Use the const
type qualifier to qualify an object whose
value cannot be changed. Objects qualified by the const
keyword cannot be modified. This means that an object declared as
const
cannot serve as the operand in an operation that
changes its value; for example, the ++ and -- operators are
not allowed on objects qualified with const
. Using the
const
qualifier on an object protects it from the side
effects caused by operations that alter storage.
The declaration of const
-qualified objects can be
slightly more complicated than that for nonqualified types. Here are
some examples, with explanatory comments:
const int x = 44; /* const qualification of int type -- the value of x cannot be modified */ const int *z; /* Pointer to a constant integer -- The value in the location pointed to by z cannot be modified */ int * const ptr; /* A constant pointer -- a pointer which will always point to the same location */ const int *const p; /* A constant pointer to a constant integer -- neither the pointer or the integer can be modified */ const const int y; /* Illegal - redundant use of const */
The following rules apply to the const
type qualifier:
const
qualifier can be used to qualify
any data type, including a single member of a structure or union.
const
is specified when declaring
an aggregate type, all members of the aggregate type are
treated as objects qualified with const
. When
const
is used to qualify a member of an aggregate
type, only that member is qualified. For example:
const struct employee { char *name; int birthdate; /* name, birthdate, job_code, and salary are */ int job_code; /* treated as though declared with const. */ float salary; } a, b; /* All members of a and b are const-qualified*/ struct employee2 { char *name; const int birthdate; /* Only this member is qualified */ int job_code; float salary; } c, d;
All members in the previous structure are qualified with
const
. If the tag employee
is
used to specify another structure later in the program,
the const
qualifier does not apply to the new
structure's members unless explicitly specified.
const
qualifier can be specified with the
volatile
qualifier. This is useful, for example, in
a declaration of a data object that is immutable by the source
process but can be changed by other processes, or as a model of a
memory-mapped input port such as a real-time clock.
const
object can be
assigned to a pointer to a const
object (with an
explicit const
specifier), but that pointer cannot
be used to alter the value of the object. For example:
const int i = 0; int j = 1; const int *p = &i; /* Explicit const specifier required */ int *q = &j; *p = 1; /* Error -- attempt to modify a const- qualified object through a pointer */ *q = 1; /* OK */
const
object using
a pointer to a non-const
qualified type causes
unpredictable behavior.
Any object whose type includes the volatile
type
qualifier indicates that the object should not be subject to
compiler optimizations altering references to, or modifications
of, the object.
volatile
objects are especially prone to side effects. (See Section 2.5.)
Optimizations that are defeated by using the volatile
specifier can be categorized as follows:
An object without the volatile
specifier does not
compel the compiler to perform these optimizations; it indicates
that the compiler has the freedom to apply the optimizations
depending on program context and compiler optimization level.
The volatile
qualifier forces the compiler to allocate
memory for the volatile
object, and to always access
the object from memory. This qualifier is often used to declare
that an object can be accessed in some way not under the compiler's
control. Therefore, an object qualified by the volatile
keyword can be modified or accessed in ways by other processes or
hardware, and is especially vulnerable to side effects.
The following rules apply to the use of the volatile
qualifier:
volatile
qualifier can be used to qualify
any data type, including a single member of a structure or union.
volatile
keyword elicits
a warning message. For example:
volatile volatile int x;
volatile
is used with an aggregate type
declaration, all members of the aggregate type are qualified
with volatile
. When volatile
is used
to qualify a member of an aggregate type, only that member is
qualified. For example:
volatile struct employee { char *name; int birthdate; /* name, birthdate, job_code, and salary are */ int job_code; /* treated as though declared with volatile. */ float salary; } a,b; /* All members of a and b are volatile-qualified */ struct employee2 { char *name; volatile int birthdate; /* Only this member is qualified */ int job_code; float salary; } c, d;
If the tag employee
is used to specify another
structure later in the program, the volatile
qualifier does not apply to the new structure's members unless
explicitly specified.
const
qualifier can be used with the
volatile
qualifier. This is useful, for example, in
a declaration of a data object that is immutable by the source
process but can be changed by other processes, or as a model of a
memory-mapped input port such as a real-time clock.
volatile
object can
be assigned to a pointer that points to a volatile
object. For example:
const int *intptr; volatile int x; intptr = &x;
Likewise, the address of a volatile
object can be
assigned to a pointer that points to a non-volatile
object.
Use this data-type qualifier in pointer definitions to indicate to the compiler that the data pointed to is not properly aligned on a correct address. (To be properly aligned, the address of an object must be a multiple of the size of the type. For example, two-byte objects must be aligned on even addresses.)
When data is accessed through a pointer declared
__unaligned
, the compiler generates the additional
code necessary to copy or store the data without causing alignment
errors. It is best to avoid use of misaligned data altogether,
but in some cases the usage may be justified by the need to access
packed structures, or by other considerations.
Here is an example of a typical use of __unaligned
:
typedef enum {int_kind, float_kind, double_kind} kind; void foo(void *ptr, kind k) { switch (k) { case int_kind: printf("%d", *(__unaligned int *)ptr); break; case float_kind: printf("%f", *(__unaligned float *)ptr); break; case double_kind: printf("%f", *(__unaligned double *)ptr); break; } }
Use the __restrict
type qualifier on the declaration of
a pointer type to indicate that the pointer is subject to compiler
optimizations. Restricted pointers are expected to be an addition
to the 9X revision of the ISO C Standard. Using restricted pointers
judiciously can often improve the quality of code output by the
compiler.
The following sections describe the rationale for restricted-pointer support.
For many compiler optimizations, ranging from simply holding a value in a register to the parallel execution of a loop, it is necessary to determine whether two distinct lvalues designate distinct objects. If the objects are not distinct, the lvalues are said to be aliases. If the compiler cannot determine whether or not two lvalues are aliases, it must assume that they are aliases and suppresses various optimizations.
Aliasing through pointers presents the greatest difficulty, because there is often not enough information available within a single function, or even within a single compilation unit, to determine whether two pointers can point to the same object. Even when enough information is available, this analysis can require substantial time and space. For example, it could require an analysis of a whole program to determine the possible values of a pointer that is a function parameter.
Consider how potential aliasing enters into implementations in
C of two Standard C library functions memmove
and
memcpy
:
memmove
, and the sample implementation that follows
adheres to the model described in the revised ISO C Standard by
copying through a temporary array.
memcpy
cannot be used for copying
between overlapping arrays, its implementation can be a direct
copy.
The following example contrasts sample implementations of the
memcpy
and memmove
functions:
/* Sample implementation of memmove */ void *memmove(void *s1, const void *s2, size_t n) { char * t1 = s1; const char * t2 = s2; char * t3 = malloc(n); size_t i; for(i=0; i<n; i++) t3[i] = t2[i]; for(i=0; i<n; i++) t1[i] = t3[i]; free(t3); return s1; } /* Sample implementation of memcpy */ void *memcpy(void *s1, const void *s2, size_t n); char * t1 = s1; const char * t2 = s2; while(n-- > 0) *t1++ = *t2++; return s1; }
The restriction on memcpy
is expressed only in its
description in the Standard, and cannot be expressed directly in its
implementation in C. While this allows the source-level optimization
of eliminating the temporary used in memmove
, it does
not provide for compiler optimization of the resulting single loop.
In many architectures, it is faster to copy bytes in blocks, rather than one at a time:
memmove
uses
malloc
to obtain the temporary array, and this
guarantees that the temporary is disjoint from the source and
target arrays. From this, a compiler can deduce that block copies
can safely be used for both loops (if the compiler recognizes
malloc
as a special function that allocates new memory).
memcpy
, on the
other hand, provides no basis for the compiler to rule
out the possibility that, for example, s1
and s2
point to successive bytes. Therefore,
unconditional use of block copies does not appear to
be safe, and the code generated for the single loop in
memcpy
might not be as fast as the code for each
loop in memmove
.
The restriction in the description of memcpy
in the
Standard prohibits copying between overlapping objects.
An object is a region of data storage, and except for
bit-fields, objects are composed of contiguous sequences of one
or more bytes, the number, order, and encoding of which are either
explicitly specified or implementation-defined.
Consider the following example:
/* memcpy between rows of a matrix */ void f1(void) { extern char a[2][N]; memcpy(a[1], a[0], N); }
In this example:
N
bytes in length (that is, treated as an array of
N
elements of character type).
memcpy
has defined behavior.
Now consider the following example:
/* memcpy between halves of an array */ void f2(void) { extern char b[2*N]; memcpy(b+N, b, N); }
In this example:
memcpy
, a contiguous sequence of
elements within an array can be regarded as an object in its
own right.
b
can be
regarded as objects in their own rights.
The length of an object is determined by various methods:
mbstowcs
, wcstombs
, strftime
, vsprintf
,
sscanf
, sprintf
, and all other
similar functions, objects and lengths are dynamically
determined.
If an aliasing restriction like the one for memcpy
could be expressed in a function definition, then it would be
available to a compiler to facilitate effective pointer alias
analysis. The __restrict
type qualifier accomplishes
this by specifying in the declaration of a pointer that the pointer
provides exclusive initial access to the object to which
it points, as though the pointer were initialized with a call to
malloc
.
The following prototype for memcpy
both expresses the
desired restriction and is compatible with the current prototype:
void *memcpy(void * __restrict s1, const void * __restrict s2, size_t n);
The following definition of restricted pointers supports expression of aliasing restrictions in as many paradigms as possible. This is helpful in converting existing programs to use restricted pointers, and allows more freedom of style in new programs.
This definition, therefore, allows restricted pointers to be:
A pointer is designated as a restricted pointer by specifying the
__restrict
type qualifier on its declaration.
The formal definition of a restricted pointer as proposed for inclusion in the revised ISO C Standard follows:
Let D be a declaration of an ordinary identifier that provides a means of designating an object P as a restrict-qualified pointer.
If D appears inside a block and does not have storage-class extern, let B denote the block. If D appears in the list of parameter declarations of a function definition, let B denote the associated block. Otherwise, let B denote the block of main (or the block of whatever function is called at program startup, in a freestanding environment).
In what follows, a pointer expression E is said to be based on object P if (at some sequence point in the execution of B prior to the evaluation of E) modifying P to point to a copy of the array object into which it formerly pointed would change the value of E. (In other words, E depends on the value of P itself rather than on the value of an object referenced indirectly through P. For example, if identifier
p
has type(int ** restrict)
, then the pointer expressionsp
andp+1
are based on the restricted pointer object designated byp
, but the pointer expressions*p
andp[1]
are not.)During each execution of B, let O be the array object that is determined dynamically by all references through pointer expressions based on P. All references to values of O shall be through pointer expressions based on P. Furthermore, if P is assigned the value of a pointer expression E that is based on another restricted pointer object P2, associated with block B2, then either the execution of B2 shall begin before the execution of B, or the execution of B2 shall end prior to the assignment. If this requirement is not met, then the behavior is undefined.
Here an execution of B means that portion of the execution of the program during which storage is guaranteed to be reserved for an instance of an object that is associated with B and has automatic storage duration. A reference to a value means either an access to or a modification of the value. During an execution of B, attention is confined to those references that are actually evaluated (this excludes references that appear in unevaluated expressions, and also excludes references that are "available," in the sense of employing visible identifiers, but do not actually appear in the text of B).
A translator is free to ignore any or all aliasing implications of uses of restrict.
The formal definition of the __restrict
type qualifier
can be difficult to grasp, but simplified explanations tend to
be less accurate and complete. The essence of the definition is
that the __restrict
type qualifier is an assertion
by the programmer that whenever a memory access is made through a
restricted pointer, the only aliases the compiler need consider are
other accesses made through the same pointer.
Much of the complexity is in defining exactly what is meant for an access to be made through a pointer (the based-on rules), and specifying how a restricted pointer can be assigned the value of another restricted pointer, while limiting the aliasing potential to occur only at block boundaries. Examples can be the best way to understand restricted pointers.
The following examples show the use of restricted pointers in various contexts.
A file scope restricted pointer is subject to very strong restrictions. It should point into a single array object for the duration of the program. That array object must not be referenced both through the restricted pointer and through either its declared name (if it has one) or another restricted pointer.
Because of these restrictions, references through the pointer can be optimized as effectively as references to a static array through its declared name. File scope restricted pointers are therefore useful in providing access to dynamically allocated global arrays.
In the following example, a compiler can deduce from the
__restrict
type qualifiers that there is no potential
aliasing among the names a
, b
, and
c
:
/* File Scope Restricted Pointer */ float * __restrict a, * __restrict b; float c[100]; int init(int n) { float * t = malloc(2*n*sizeof(float)); a = t; /* a refers to 1st half. */ b = t + n; /* b refers to 2nd half. */ }
Notice how the single block of allocated storage is subdivided into
two unique arrays in the function init
.
Restricted pointers are also very useful as pointer parameters of a function. Consider the following example:
/* Restricted pointer function parameters */ float x[100]; float *c; void f3(int n, float * __restrict a, float * const b) { int i; for ( i=0; i<n; i++ ) a[i] = b[i] + c[i]; } void g3(void) { float d[100], e[100]; c = x; f3(100, d, e); /* Behavior defined. */ f3( 50, d, d+50); /* Behavior defined. */ f3( 99, d+1, d); /* Behavior undefined. */ c = d; f3( 99, d+1, e); /* Behavior undefined. */ f3( 99, e, d+1); /* Behavior defined. */ }
In the function f3
, it is possible for a compiler
to infer that there is no aliasing of modified objects, and so to
optimize the loop aggressively. Upon entry to f3
, the
restricted pointer a
must provide exclusive access
to its associated array. In particular, within f3
neither b
nor c
may point into the array
associated with a
, because neither is assigned a
pointer value based on a
. For b
,
this is evident from the const
qualifier in its
declaration, but for c
, an inspection of the body
of f3
is required.
Two of the calls shown in g3
result in aliasing that is
inconsistent with the __restrict
qualifier, and their
behavior is undefined. Note that it is permitted for c
to point into the array associated with b
. Note also
that, for these purposes, the "array" associated with a particular
pointer means only that portion of an array object that is actually
referenced through that pointer.
A block-scope restricted pointer makes an aliasing assertion that is limited to its block. This is more natural than allowing the assertion to have function scope. It allows local assertions that apply only to key loops, for example. It also allows equivalent assertions to be made when inlining a function by converting it into a macro.
In the following example, the original restricted-pointer parameter is represented by a block-scope restricted pointer:
/* Macro version of f3 */ float x[100]; float *c; #define f3(N, A, B) \ { int n = (N); \ float * __restrict a = (A); \ float * const b = (B); \ int i; \ for ( i=0; i<n; i++ ) \ a[i] = b[i] + c[i]; \ }
A restricted-pointer member of a structure makes an aliasing assertion. The scope of that assertion is the scope of the ordinary identifier used to access the structure.
Therefore, although the structure type is declared at file scope
in the following example, the assertions made by the declarations
of the parameters of f4
have block (of the function)
scope.
/* Restricted pointers as members of a structure */ struct t { /* Restricted pointers assert that */ int n; /* members point to disjoint storage. */ float * __restrict p; float * __restrict q; }; void f4(struct t r, struct t s) { /* r.p, r.q, s.p, s.q should all point to */ /* disjoint storage during each execution of f4. */ /* ... */ }
A __restrict
qualifier in a typedef
makes an aliasing assertion when the typedef
name is
used in the declaration of an ordinary identifier that provides
access to an object. As with members of structures, the scope of the
latter identifier, not the scope of the typedef
name,
determines the scope of the aliasing assertion.
Consider the following example:
/* Pointer expressions based on p */ #include <stdlib.h> #include <string.h> struct t { int * q; int i; } a[2] = { /* ... */ }; void f5(struct t * __restrict p, int c) { struct t * q; int n; if(c) { struct t * r; r = malloc(2*sizeof(*p)); memcpy(r, p, 2*sizeof(*p)); p = r; } q = p; n = (int)p; /* - - - - - - - - - - - - - - - - - - - - - - - Pointer expressions Pointer expressions based on p: not based on p: ------------------- ------------------- p p->q p+1 p[1].q &p[1] &p &p[1].i q q->p ++q (char *)p (char *)(p->i) (struct t *)n ((struct t *)n)->q - - - - - - - - - - - - - - - - - - - - - - - - */ } main() { f5(a, 0); f5(a, 1); }
In this example, the restricted pointer parameter p
is potentially adjusted to point into a copy of its original array
of two structures. By definition, a subsequent pointer expression
is said to be based on p
if and only if its value is
changed by this adjustment.
In the comment:
p
.
p
.
This can be verified by adding appropriate print statements for the
expressions and comparing the values produced by the two calls of
f5
in main
.
Notice that the definition of "based on" applies to expressions that
rely on implementation-defined behavior. This is illustrated in the
example, which assumes that the casts (int)
followed by
(struct t *)
give the original value.
Consider one restricted pointer "newer" than another if the block with which the first is associated begins execution after the block associated with the second. Then the formal definition allows a newer restricted pointer to be assigned a value based on an older restricted pointer. This allows, for example, a function with a restricted-pointer parameter to be called with an argument that is a restricted pointer.
Conversely, an older restricted pointer can be assigned a value based on a newer restricted pointer only after execution of the block associated with the newer restricted pointer has ended. This allows, for example, a function to return the value of a restricted pointer that is local to the function, and the return value then to be assigned to another restricted pointer.
The behavior of a program is undefined if it contains an assignment between two restricted pointers that does not fall into one of these two categories. Some examples follow:
/* Assignments between restricted pointers */ int * __restrict p1, * __restrict p2; void f6(int * __restrict q1, * __restrict q2) { q1 = p1; /* Valid behavior */ p1 = p2; /* Behavior undefined */ p1 = q1; /* Behavior undefined */ q1 = q2; /* Behavior undefined */ { int * __restrict r1, * __restrict r2; ... r1 = p1; /* Valid behavior */ r1 = q1; /* Valid behavior */ r1 = r2; /* Behavior undefined */ q1 = r1; /* Behavior undefined */ p1 = r1; /* Behavior undefined */ ... } }
The value of a restricted pointer can be assigned to an unrestricted pointer, as in the following example:
/* Assignments to unrestricted pointers */ void f7(int n, float * __restrict r, float * __restrict s) { float * p = r, * q = s; while(n-- > 0) *p++ = *q++; }
The DEC C compiler tracks pointer values and optimizes the loop
as effectively as if the restricted pointers r
and
s
were used directly, because in this case it is easy
to determine that p
is based on r
, and
q
is based on s
.
More complicated ways of combining restricted and unrestricted pointers are unlikely to be effective because they are too difficult for a compiler to analyze. As a programmer concerned about performance, you must adapt your style to the capabilities of the compiler. A conservative approach would be to avoid using both restricted and unrestricted pointers in the same function.
Except where specifically noted in the formal definition, the
__restrict
qualifier behaves in the same way as
const
and volatile
.
In particular, it is not a constraint violation for a function return type or the type-name in a cast to be qualified, but the qualifier has no effect because function call expressions and cast expressions are not lvalues.
Thus, the presence of the __restrict
qualifier in the
declaration of f8
in the following example makes no
assertion about aliasing in functions that call f8
:
/* Qualified function return type and casts */ float * __restrict f8(void) /* No assertion about aliasing. */ { extern int i, *p, *q, *r; r = (int * __restrict)q; /* No assertion about aliasing. */ for(i=0; i<100; i++) *(int * __restrict)p++ = r[i]; /* No assertion */ /* about aliasing. */ return p; }
Similarly, the two casts make no assertion about aliasing of the
references through the pointers p
and r
.
It is a constraint violation to restrict-qualify an object type that is not a pointer type, or to restrict-qualify a pointer to a function:
/*__restrict cannot qualify non-pointer object types: */ int __restrict x; /* Constraint violation */ int __restrict *p; /* Constraint violation */ /* __restrict cannot qualify pointers to functions: */ float (* __restrict f9)(void); /* Constraint violation */