Tru64 UNIX
Compaq C Language Reference Manual


Previous Contents Index

3.7.4.3.8 Assignments to Unrestricted Pointers

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 Compaq 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.

3.7.4.3.9 Ineffective Uses of Type Qualifiers

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 .

3.7.4.3.10 Constraint Violations

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 */ 

3.8 Type Definition

The keyword typedef is used to define a type synonym. In such a definition, the identifiers name types instead of objects. One such use is to define an abbreviated name for a lengthy or confusing type definition.

A type definition does not create a new basic data type; it creates an alias for a basic or derived type. For example, the following code helps explain the data types of objects used later in the program:


typedef float *floatp, (*float_func_p)(); 

The type floatp is now "pointer to a float value" type, and the type float_func_p is "pointer to a function returning float ".

A type definition can be used anywhere the full type name is normally used (you can, of course, use the normal type name). Type definitions share the same name space as variables, and defined types are fully compatible with their equivalent types. Types defined as qualified types inherit their type qualifications.

Type definitions can also be built from other type definitions. For example:


typedef char byte; 
typedef byte ten_bytes[10]; 

Type definition can apply to variables or functions. It is illegal to mix type definitions with other type specifiers. For example:


typedef int *int_p; 
typedef unsigned int *uint_p; 
unsigned int_p x;           /*  Invalid   */ 
uint_p y;                   /*  Valid     */ 

Type definitions can also be used to declare function types. However, the type definition cannot be used in the function's definition. The function's return type can be specified using a type definition. For example:


typedef unsigned *uint_p;   /* uint_p has type "pointer to unsigned int"    */ 
uint_p xp; 
typedef uint_p func(void);  /* func has type "function returning pointer to */ 
                            /* unsigned int                                 */ 
func f; 
func b; 
  func f(void)              /* Invalid -- this declaration specifies a      */ 
                            /* function returning a function type, which    */ 
  {                         /* is not allowed                               */ 
    return xp; 
  } 
 
uint_p b(void)              /* Legal - this function returns a value of 
  {                         /* type uint_p.                                 */ 
   return xp; 
  } 

The following example shows that a function definition cannot be inherited from a typedef name:


typedef int func(int x); 
func f;        
func f         /*  Valid definition of f with type func                  */ 
{ 
  return 3; 
}              /* Invalid, because the function's type is not inherited  */ 

Changing the previous example to a valid form results in the following:


typedef int func(int x); 
func f;      
int f(int x)   /*  Valid definition of f with type func           */ 
{ 
  return 3; 
}              /* Legal, because the function's type is specified */ 

You can include prototype information, including parameter names, in the typedef name. You can also redefine typedef names in inner scopes, following the scope rules explained in Section 2.3.


Chapter 4
Declarations

Declarations are used to introduce the identifiers used in a program and to specify their important attributes, such as type, storage class, and identifier name. A declaration that also causes storage to be reserved for an object or that includes the body of a function, is called a definition.

Section 4.1 covers general declaration syntax rules, Section 4.2 discusses initialization, and Section 4.3 describes external declarations.

The following kinds of identifiers can be declared. See the associated section for information on specific declaration and initialization syntax. Functions are discussed in Chapter 5.

Note

Preprocessor macros created with the #define directive are not declarations. Chapter 8 has information on creating macros with preprocessor directives.

4.1 Declaration Syntax Rules

The general syntax of a declaration is as follows:

declaration:

declaration-specifiers init-declarator-listopt;

declaration-specifiers:

storage-class-specifier declaration-specifiersopt
type-specifier declaration-specifiersopt
type-qualifier declaration-specifiersopt

init-declarator-list:

init-declarator
init_declarator-list , init-declarator

init-declarator:

declarator
declarator = initializer

Note the following items about the general syntax of a declaration:

Consider the following example:


volatile static int data = 10; 

This declaration shows a qualified type (a data type with a type qualifier -- in this case, int qualified by volatile ), a storage class ( static ), a declarator ( data ), and an initializer ( 10 ). This declaration is also a definition, because storage is reserved for the data object data .

The previous example is simple to interpret, but complex declarations are more difficult. See your platform-specific Compaq C documentation for more information about interpreting C declarations.

The following semantic rules apply to declarations:

Storage Allocation

Storage is allocated to a data object in the following circumstances:

Note

The compiler does not necessarily allocate distinct variables to memory locations according to the order of declaration in the source code. Furthermore, the order of allocation can change as a result of seemingly unrelated changes to the source code, command-line options, or from one version of the compiler to the next - it is essentially unpredictable. The only way to control the placement of variables relative to each other is to make them members of the same struct type.

4.2 Initialization

Initializers provide an initial value for objects, and follow this syntax:

initializer:

assignment-expr
{ initializer-list }
{ initializer-list, }

initializer-list:

designation-opt initializer
initializer-list, designation-opt initializer

designation:

designator-list =

designator-list:

designator
designator-list designator

designator:

[ constant-expr ]
. identifier

Initialization of objects of each type is discussed in the following sections, but a few universal constraints apply to all initializations in C:

C has historically allowed initializers to be optionally surrounded by extra braces (to improve formatting clarity, for instance). These initializers are parsed differently depending on the type of parser used. Compaq C uses the parsing technique specified by the ANSI standard, known as the top-down parse. Programs depending on a bottom-up parse of partially braced initializers can yield unexpected results. The compiler generates a warning message when it encounters unnecessary braces in common C compatibility mode or when the error-checking compiler option is specified on the command line.

4.3 External Declarations

An object declaration outside of a function is called an external declaration. Contrast this with an internal declaration, which is a declaration made inside a function or block; the declaration is internal to that function or block, and is visible only to that function or block. The compiler recognizes an internally declared identifier from the point of the declaration to the end of the block.

If an object's declaration has file scope and an initializer, the declaration is also an external definition for the object. A C program consists of a sequence of external definitions of objects and functions.

Any definition reserves storage for the entity being declared. For example:


float fvalue = 15.0;                /* external definition  */ 
main () 
{ 
  int ivalue = 15;                  /* internal definition  */ 
} 

External data declarations and external function definitions take the same form as any data or function declaration (see Chapter 5 for standard function declaration syntax), and must follow these rules:

Note

An external function can be called without previously declaring it in C, but this construction is not recommended because of the loss of type checking and subsequent susceptibility to bugs. If such a function call is made, the compiler will treat the function as if an external declaration of type int appeared in the block containing the call. For example:


void function1() 
{ 
int a,b; 
x (a,b); 
} 


Here, the compiler will behave as if the declaration extern int x(); appeared within the function1 definition block.

The first declaration of an identifier in a compilation unit must specify, explicitly or by the omission of the static keyword, whether the identifier is internal or external. For each object, there can be only one definition. Multiple declarations of the same object may be made, as long as there are no conflicting or duplicate definitions for the same object.

An external object may be defined with either an explicit initialization or a tentative definition. A declaration of an object with file scope, without an initializer, and with a storage-class specifier other than static is a tentative definition. The compiler will treat a tentative definition as the object's only definition unless a complete definition for the object is found. As with all declarations, storage is not actually allocated until the object is defined.

If a compilation unit contains more than one tentative definition for an object, and no external definition for the object, the compiler treats the definition as if there were a file scope declaration of the object with an initializer of zero, with composite type as of the end of the compilation unit. See Section 2.7 for a definition of composite type.

If the declaration of an object is a tentative definition and has internal linkage, the declared type must not be an incomplete type. See Section 2.9 for examples of tentative definitions.


Previous Next Contents Index