11.1. General SyntaxThere are several different kinds of declarations:
Declarations of enumerated, structure, and union types are described in Chapter 2 and Chapter 10. This chapter deals mainly with object, function, and typedef declarations. These declarations contain a declarator list with one or more declarators. Each declarator declares a typedef name or an identifier for an object or a function. The general form of this kind of declaration is: [typedef | storage_class_specifier] type declarator [, declarator [, ...]]; The parts of this syntax are as follows:
11.1.1. ExamplesLet us examine some examples of object and function declarations. We discuss declarations of typedef names in "typedef Declarations," later in this chapter. In the following example, the declarator list in the first line contains two declarators, one of which includes an initializer. The line declares two objects, iVar1 and iVar2, both with type int. iVar2 begins its existence with the value 10. int iVar1, iVar2 = 10; static char msg[ ] = "Hello, world!"; The second line in this example defines and initializes an array of char named msg with static storage duration (we discuss storage duration in the following section). Next, you see the declaration of an external variable named status with the qualified type volatile short: extern volatile short status; The next declaration defines an anonymous enumerated type with the enumeration constants OFF and ON, as well as the variable toggle with this type. The declaration initializes toggle with the value ON: enum { OFF, ON } toggle = ON; The following example defines the structure type struct CharColor, whose members are the bit-fields fg, bg, and bl. It also defines the variable attribute with this type, and initializes the members of attribute with the values 12, 1, and 0. struct CharColor { unsigned fg:4, bg:3, bl:1; } attribute = { 12, 1, 0 }; The second line of the next example defines an array named clientArray with 100 elements of type struct Client, and a pointer to struct Client named clientPtr, initialized with the address of the first element in clientArray: struct Client { char name[64], pin[16]; /* ... */ }; // A structure type. struct Client clientArray[100], *clientPtr = clientArray; Next you see a declaration of a float variable, x, and an array, flPtrArray, whose 10 elements have the type pointer to float. The first of these pointers, flPtrArray[0], is initialized with &x; the remaining array elements are initialized as null pointers. float x, *flPtrArray[10] = { &x }; The following line declares the function func1( ) with the return value type int. This declaration offers no information about the number and types of the function's parameters, if any. int func1( ); We'll move on to the declaration of a static function named func2( ), whose only parameter has the type pointer to double, and which also returns a pointer to double: static double *func2( double * ); Last, we define the inline function printAmount( ), with two parameters, returning int. inline int printAmount( double amount, int width ) { return printf( "%*.2lf", width, amount ); } 11.1.2. Storage Class SpecifiersA storage class specifier in a declaration modifies the linkage of the identifier (or identifiers) declared, and the storage duration of the corresponding objects. (The concepts of linkage and storage duration are explained individually in later sections of this chapter.) No more than one storage class specifier may appear in a declaration. Function identifiers may be accompanied only by the storage class specifier extern or static. Function parameters may take only the storage class specifier register. The four storage class specifiers have the following meanings:
11.1.3. Type QualifiersYou can modify types in a declaration by including the type qualifiers const, volatile, and restrict. A declaration may contain any number of type qualifiers in any order. A type qualifier list may even contain the same type qualifier several times, or the same qualifier may be applied repeatedly through qualified typedef names. The compiler ignores such repetitions of any qualifier, treating them as if the qualifier were present only once. The individual type qualifiers have the following meanings:
The compiler may store objects qualified as const, but not volatile, in a read-only segment of memory. It may also happen that the compiler allocates no storage for such an object if the program does not use its address. Objects qualified with both const and volatile, such as the object ticks in the following example, cannot be modified by the program itself, but may be modified by something else, such as a clock chip's interrupt handler: extern const volatile int ticks; Here are some more examples of declarations using qualified types: const int limit = 10000; // A constant int object. typedef struct { double x, y, r; } Circle; // A structure type. const Circle unit_circle = { 0, 0, 1 }; // A constant Circle object. const float v[ ] = { 1.0F, 0.5F, 0.25F }; // An array of constant // float elements. volatile short * restrict vsPtr; // A restricted pointer to volatile short. With pointer types, the type qualifiers to the right of the asterisk qualify the pointer itself, while those to the left of the asterisk qualify the type of object it points to. In the last example, the pointer vsPtr is qualified with restrict, and the object it points to with volatile. For more details, including more about restricted pointers, see "Pointers and Type Qualifiers" in Chapter 9. 11.1.4. Declarations and DefinitionsYou can declare an identifier as often as you want, but only one declaration within its scope can be a definition. Placing the definitions of objects and functions with external linkage in header files is a common way of introducing duplicate definitions, and is therefore not a good idea. An identifier's declaration is a definition in the following cases:
11.1.5. Complex DeclaratorsThe symbols ( ), [ ], and * in a declarator specify that the identifier has a function, array, or pointer type. A complex declarator may contain multiple occurrences of any or all of these symbols. This section explains how to interpret such declarators. The basic symbols in a declarator have the following meanings:
In declarators, these symbols have the same priority and associativity as the corresponding operators would have in an expression. Furthermore, as in expressions, you can use additional parentheses to modify the order in which they are interpreted. An example: int *abc[10]; // An array of 10 elements whose type is pointer to int. int (*abc)[10]; // A pointer to a array of 10 elements whose type is int. In a declarator that involves a function type, the parentheses that indicate a function may contain the parameter declarations. The following example declares a pointer to a function type: int (*fPtr)(double x); // fPtr is a pointer to a function that has // one double parameter and returns int. The declarator must include declarations of the function parameters if it is part of the function definition. When interpreting a complex declarator, always begin with the identifier. Starting from there, repeat the following steps in order until you have interpreted all the symbols in the declarator:
Here is an example: extern char *(* fTab[ ])(void); Table 11-1 interprets this example bit by bit. The third column is meant to be read from the top row down, as a sentence.
fTab has an incomplete array type, because the declaration does not specify the array length. Before you can use the array, you must define it elsewhere in the program with a specific length. The parentheses around * fTab[ ] are necessary. Without them, fTab would be declared as an array whose elements are functionswhich is impossible. The next example shows the declaration of a function identifier, followed by its interpretation: float (* func( ))[3][10];
In other words, the function func returns a pointer to a two-dimensional array of 3 rows and 10 columns. Here again, the parentheses around * func( ) are necessary, as without them the function would be declared as returning an arraywhich is impossible. |