HPlogo HP C/HP-UX Reference Manual: Version A.05.55.02 > Chapter 3 Data Types and Declarations

Declarators

» 

Technical documentation

Complete book in PDF

 » Table of Contents

 » Index

A declarator introduces an identifier and specifies its type, storage class, and scope.

Syntax

declarator ::=
      [pointer] direct-declarator

direct-declarator ::=
      identifier
      (declarator)
      direct-declarator [[constant-expression]]
      direct-declarator (parameter-type-list)
      direct-declarator ([identifier-list])
pointer ::=
      * [type-qualifier-list]
      * [type-qualifier-list] pointer

type-qualifier-list ::=
      type-qualifier
      type-qualifier-list type-qualifier

parameter-type-list ::=
      parameter-list
      parameter-list , ...

parameter-list ::=
      parameter-declaration
      parameter-list , parameter-declaration

parameter-declaration ::=
      declaration-specifiers declarator
      declaration-specifiers [abstract-declarator]

identifier-list ::=
      identifier
      identifier-list , identifier

Description

Various special symbols may accompany declarators. Parentheses change operator precedence or specify functions. The asterisk specifies a pointer. Square brackets indicate an array. The constant-expression specifies the size of an array.

A declarator specifies one identifier and may supply additional type information. When a construction with the same form as the declarator appears in an expression, it yields an entity of the indicated scope, storage class, and type.

If an identifier appears by itself as a declarator, it has the type indicated by the type specifiers heading the declaration.

Declarator operators have the same precedence and associativity as operators appearing in expressions. Function declarators and array declarators bind more tightly than pointer declarators. You can change the binding of declarator operators using parentheses. For example,

int *x[10];

is an array of 10 pointers to ints. This is because the array declarator binds more tightly than the pointer declarator. The declaration

int (*x)[10];

is a single pointer to an array of 10 ints. The binding order is altered with the use of parentheses.

Pointer Declarators

If D is a declarator, and T is some combination of type specifiers and storage class specifiers (such as int), then the declaration T *D declares D to be a pointer to type T. D can be any general declarator of arbitrary complexity. For example, if D were declared as a pointer already, the use of a second asterisk indicates that D is a pointer to a pointer to T.

Some examples:

int *pi; /* pi: Pointer to an int */
int **ppi; /* ppi: Pointer to a pointer to an int */
int *ap[10]; /* ap: Array of 10 pointers to ints */
int (*pa)[10]; /* pa: Pointer to array of 10 ints */
int *fp(); /* fp: Function returning pointer to int */
int (*pf)(); /* pf: Pointer to function returning an int */

The binding of * (pointer) declarators is of lower precedence than either [ ] (array) or () (function) declarators. For this reason, parentheses are required in the declarations of pa and pf.

Array Declarators

If D is a declarator, and T is some combination of type specifiers and storage class specifiers (such as int), then the declaration

T D[constant-expression];

declares D to be an array of type T.

You declare multidimensional arrays by specifying additional array declarators. For example, a 3 by 5 array of integers is declared as follows:

int x[3][5];

This notation (correctly) suggests that multidimensional arrays in C are actually arrays of arrays. Note that the [ ] operator groups from left to right. The declarator x[3][5] is actually the same as ((x[3])[5]). This indicates that x is an array of three elements each of which is an array of five elements. This is known as row-major array storage.

You can omit the constant-expression giving the size of an array under certain circumstances. You can omit the first dimension of an array (the dimension that binds most tightly with the identifier) in the following cases:

  • If the array is a formal parameter in a function definition.

  • If the array declaration contains an initializer.

  • If the array declaration has external linkage and the definition (in another translation unit) that actually allocates storage provides the dimension.

Note that the long long data type cannot be used to declare an array's size.

Following are examples of array declarations:

int x[10];              /* x:  Array of 10 integers */
float y[10][20];        /* y: Matrix of 10x20 floats */
extern int z[ ];        /* z: External integer array of undefined dimension */
int a[ ]={2,7,5,9};     /* a: Array of 4 integers */
int m[ ][3]= {          /* m: Matrix of 2x3 integers */
   {1,2,7},
   {6,6,6} };

Note that an array of type T that is the formal parameter in a function definition has been converted to a pointer to type T. The array name in this case is a modifiable lvalue and can appear as the left operand of an assignment operator. The following function will clear an array of integers to all zeros. Note that the array name, which is a parameter, must be a modifiable lvalue to be the operand of the ++ operator.

void clear(a, n)
int a[];            /* has been converted to int * */
int n;              /* number of array elements to clear */
{
   while(n--)       /* for the entire array */
   *a++ = 0;        /* clear each element to zero */
}

Variable Length Array

Variable Length Array(VLA) is a part of C99 standards (ISO/IEC 9899:1999). VLA allows the integer expression delimited by [ and ] in an array declarator to be a variable expression or *. An identifier whose declaration has such an array declarator is a variably modified (VM) type.

All identifiers having a VM type must be either:

  • block scope or function prototype scope, if the expression in an array declarator is a variable expression or

  • function prototype scope, if the expression is *.

NOTE: VLA is supported only in ANSI extended (-Ae) mode.

Arrays declared with the static or extern storage class specifier cannot have a VM type. But a pointer to an array declared with the static storage class specifier can have a VM type. All identifiers having a VM type are ordinary identifiers and therefore cannot be the members of structures or unions.

extern int n;

int A[n];                     // Error - file scope VM type
extern int (*p) [n];          // Error - file scope VM type
int B[100];                   // OK - file scope but not VM type
void foo(int m, int C[m]      // OK - function prototype
                              // scope VM type
{
   typedef int VLA[m] [m];    // OK - block scope VM type
   int D[m];                  // OK - block scope with VM type
   static int E[m];           // Error - static specifier in VM type
   extern int F[m];           // Error - extern specifier in VM type
   int (*q)[m];               // OK - block scope with VM type
   extern int (*r)[m];        // Error - extern specifier in VM type
   static int (*s)[m] = &B;   // OK - static specifier allowed in VM
                              // type since ‘s’ is pointer to array
struct tag {
   int (*x)[n];               // Error - x not ordinary identifier
   int y[n];                  // Error - y not ordinary identifier
   };
}

A goto statement is not allowed to jump past any declarations of identifiers having a VM type. A jump within the scope is permitted.

goto L1;             // Error - going INTO scope of VM type
   {
      int a[n];
      a[j] = 4;
   L1:
      a[j] = 3;
      goto L2;       // OK, going WITHIN scope of VM type
      a[j] = 5;
   L2:
      a[j] = 6;
   }
   goto L2;            // Error - going INTO scope of VM type

The size of an object having a VM type is determined and fixed at the point of the object's declaration and cannot be altered.

The following example assumes size of (int) = 4,

   int n = 10, vla[n];
   n = 20;
   printf(""%d", sizeof(vla);      // prints 40, not 80

Function Declarators

If D is a declarator, and T is some combination of type specifiers and storage class specifiers (such as int), then the declaration

T D (parameter-type-list)

or

T D ([identifier-list])

declares D to be a function returning type T. A function can return any type of object except an array or a function. However, functions can return pointers to functions or arrays.

If the function declarator uses the form with the parameter-type-list, it is said to be in prototype form. The parameter type list specifies the types of, and may declare identifiers for, the parameters of the function. If the list terminates with an ellipsis (,...), no information about the number of types of the parameters after the comma is supplied. The special case of void as the only item in the list specifies that the function has no parameters.

If a function declarator is not part of a function definition, the optional identifier-list must be empty.

Function declarators using prototype form are only allowed in ANSI mode.

Functions can also return structures. If a function returns a structure as a result, the called function copies the resulting structure into storage space allocated in the calling function. The length of time required to do the copy is directly related to the size of the structure. If pointers to structures are returned, the execution time is greatly reduced. (But beware of returning a pointer to an auto struct — the struct will disappear after returning from the function in which it is declared.)

The function declarator is of equal precedence with the array declarator. The declarators group from left to right. The following are examples of function declarators:

int f();             /* f:   Function returning an int                 */
int *fp();           /* fp: Function returning pointer to an int      */
int (*pf)();         /* pf: Pointer to function returning an int       */
int (*apf[])();        /* apf: Array of pointers to functions returning int  */

Note that the parentheses alter the binding order in the declarations of pf and apf in the above examples.

© Hewlett-Packard Development Company, L.P.