[an error occurred while processing this directive]

HP OpenVMS Systems

C Programming Language
Content starts here HP C

HP C
Language Reference Manual


Previous Contents Index

4.11 Declaring Type Definitions

In a declaration whose storage-class specifier is typedef , each declarator defines a typedef name that specifies an alias for the stated type. A typedef declaration does not introduce a new type, but only introduces a synonym for the stated type. For example:


typedef int integral_type; 
integral_type x; 

In the previous example, integral_type is defined as a synonym for int , and so the following declaration of x declares x to be of type int . Type definitions are useful in cases where a long type name (such as some forms of structures or unions) benefits from abbreviation, and in cases where the interpretation of the type can be made easier through a type definition.

A typedef name shares the same name space as other identifiers in ordinary declarators. If an object is redeclared in an inner scope, or is declared as a member of a structure or union in the same or inner scope, the type specifiers cannot be omitted from the inner declaration. For example:


typedef signed int t; 
typedef int plain; 
struct tag { 
   unsigned t:4; 
   const t:5; 
   plain r:5; 
}; 

It is evident that such constructions are obscure. The previous example declares a typedef name t with type signed int , a typedef name plain with type int , and a structure with three bit-field members, one named t , another unnamed member, and a third member named r . The first two bit-field declarations differ in that unsigned is a type specifier, which forces t to be the name of a structure member by the rule previously given. The second bit-field declaration includes const , a type qualifier, which only qualifies the still-visible typedef name t .

The following example shows additional uses of the typedef keyword:


typedef int miles, klicksp(void); 
typedef struct { double re, im; } complex; 
   .
   .
   .
miles distance; 
extern klicksp *metricp; 
complex x; 
complex z, *zp; 

All of the code shown in the previous example is valid. The type of distance is int , the type of metricp is a pointer to a function with no parameters returning int , and the type of x and z is the specified structure. zp is a pointer to the structure.

It is important to note that any type qualifiers used with a typedef name become part of the type definition. If the typedef name is later qualified with the same type qualifier, an illegal construction results. For example:


typedef const int x; 
const x y;            /*  Illegal -- duplicate qualifier used  */ 


Chapter 5
Functions

A C program is a collection of user-defined and system-defined functions. Functions provide a convenient way to break large computing tasks into smaller ones, which helps in designing modular programs that are easier to understand and maintain. A function contains zero or more statements to be executed when it is called, can be passed zero or more arguments, and can return a value.

This chapter discusses the following information about C functions:

5.1 Function Calls

A function call is a primary expression, usually a function identifier followed by parentheses, that is used to invoke a function. The parentheses contain a (possibly empty) comma-separated list of expressions that are the arguments to the function. The following is an example of a call to the function power , assuming this function is appropriately defined:


main() 
{ 
   .
   .
   .
y = power(x,n);                     /* function call */ 
} 

See Section 6.3.2 for more information on function calls.

5.2 Function Types

A function has the derived type "function returning type". The type can be any data type except array types or function types, although pointers to arrays and functions can be returned. If the function returns no value, its type is "function returning void ", sometimes called a void function. A void function in C is equivalent to a procedure in Pascal or a subroutine in FORTRAN. A non-void function in C is equivalent to a function in these other languages.

Functions can be introduced into a program in one of two ways:

  • A function definition can create a function designator, define its parameters and their type, define the type of its return value, and supply the body of the function. In the following example, power is a function returning int :


    int power(int base, int exp)     
    {  
     int n=1; 
     
     if (exp < 0) 
     { 
      printf ("Error: Cannot handle negative exponent\n"); 
      return -1; 
     } 
     
     for ( ; exp; exp--)  
         n = base * n; 
     
     return n; 
    } 
    

    See Section 5.3 for more information on function definitions.
  • A function declaration announces the properties of a function defined elsewhere. In the following example, the function main declares and calls the function power ; the definition of the function, where the code is defined, exists elsewhere:


    main() 
    {  
    int power(int base, int exp);       /* function declaration  */ 
    int x, n, y; 
       .
       .
       .
    y = power(x,n);                     /* function call         */ 
    } 
     
    

    This style of function declaration, in which the parameters are declared in a parameter type list, is called a function prototype. Function prototypes require the compiler to check function arguments for consistency with their parameters, and to convert arguments to the declared types of the parameters.
    See Sections 5.4 and 5.5 for more information on function declarations and prototypes.

5.3 Function Definitions

A function definition includes the code for the function. Function definitions can appear in any order, and in one source file or several, although a function cannot be split between files. Function definitions cannot be nested.

A function definition has the following syntax:

function-definition:


declaration-specifiersopt declarator declaration-listopt compound-statement

declaration-specifiers

The declaration-specifiers (storage-class-specifier, type-qualifier, and type-specifier) can be listed in any order. All are optional.

By default, the storage-class-specifier is extern . The static specifier is also allowed. See Section 2.10 for more information on storage-class specifiers.

ANSI allows the type-qualifier to be const or volatile , but either qualifier applied to a function return type is meaningless, because functions can only return rvalues and the type qualifiers apply only to lvalues.

The type-specifier is the data type of the value returned by the function. If no return type is specified, the function is declared to return a value of type int . A function can return a value of any type except "array of type" or "function returning type". Pointers to arrays and functions can be returned. The value returned, if any, is specified by an expression in a return statement. Executing a return statement terminates function execution and returns control to the calling function. For functions that return a value, any expression with a type compatible with the function's return type can follow return using the following format:


return expression;

If necessary, the expression is converted to the return type of the function. Note that the value returned by a function is not an lvalue. A function call, therefore, cannot constitute the left side of an assignment operator.

The following example defines a function returning a character:


char letter(char param1) 
{ 
   .
   .
   .
   return param1; 
} 

The calling function can ignore the returned value. If no expression is specified after return , or if a function terminates by encountering the right brace, then the return value of the function is undefined. No value is returned in the case of a void function.

If a function does not return a value, or if the function is always called from within a context that does not require a value, a return type of void should be specified:


void message() 
{ 
   printf("This function has no return value."); 
   return; 
} 

Specifying a return type of void in a function definition or declaration generates an error under the following conditions:

  • If the function attempts to return a value, an error occurs at the offending return statement.
  • If the void function is called in a context that requires a value, an error occurs at the function call site.

declarator

The declarator specifies the name of the function being declared. A declarator can be as simple as a single identifier, such as f1 in the following example:


int f1(char p2) 

In the following example, f1 is a "function returning int ". A declarator can also be a more complex construct, as in the following example:


int (*(*fpapfi(int x))[5])(float) 

In this example, fpapfi is a "function (taking an int argument) returning a pointer to an array of five pointers to functions (taking a float argument) returning int ". See Chapter 4 for information on specific declarator syntax.

The declarator (function) need not have been previously declared. If the function was previously declared, the parameter types and return type in the function definition must be identical to the previous function declaration.

The declarator can include a list of the function's parameters. In HP C, up to 253 parameters can be specified in a comma-separated list enclosed in parentheses. Each parameter has the auto storage class by default, although register is also allowed. There is no semicolon after the right parenthesis of the parameter list.

There are two methods of specifying function parameters:

  • The new or prototype style, which includes a parameter type list. For example:


    int f1(char a, int b) 
    { 
    function body
    } 
    
  • The old style, which includes an identifier list; the parameter types are defined in a separate declaration-list within the function definition, before the left brace that begins the function body. For example:


    int f1(a, b) 
    char a; 
    int b; 
    { function body
    } 
    

    Any undeclared parameters are assumed to be of type int .

A function definition with no parameters is defined with an empty parameter list. An empty parameter list is specified in either of two ways:

  • Using the keyword void if the prototype style is used. For example:


    char msg(void) 
    { 
    return 'a'; 
    } 
    
  • Using empty parentheses if the old style is used. For example:


    char msg() 
    { 
    return 'a'; 
    } 
    

A function defined using the prototype style establishes a prototype for that function. The prototype must agree with any preceding or following declarations of the same function.

A function defined using the old style does not establish a prototype, but if a prototype exists because of a previous declaration for that function, the parameter declarations in the definition must exactly match those in the prototype after the default argument promotions are applied to the parameters in the definition.

Avoid mixing old style and prototype style declarations and definition for a given function. It is allowed but not recommended.

See Section 5.6 for more information on function parameters and arguments. See Section 5.5 for more information on function prototypes.

compound-statement

The compound-statement is the group of declarations and statements surrounded by braces in a function or loop body. This compound statement is also called the function body. It begins with a left brace ({) and ends with a right brace (}), with any valid C declarations and statements in between. One or more return statements can be included, but they are not required.

5.4 Function Declarations

A function can be called without declaring it if the function's return value is int (although this practice is not recommended due to the loss of type-checking capability; all functions should be declared). If the return value is anything else, and if the function definition is located after the calling function in the source code, the function must be declared before calling it. For example:


char lower(int c);                    /* Function declaration */ 
 
caller()                              /* Calling function     */ 
{ 
int c; 
char c_out;      
      . 
      . 
      . 
c_out = lower(c);                     /* Function call        */ 
 
} 
 
char lower(int c_up)                  /* Function definition  */ 
{ 
   .
   .
   .
} 

If the function definition for lower was located before the function caller in the source code, lower would not have to be declared again before calling it. In that case, the function definition would serve as its own declaration and would be in scope for any function calls from within all subsequently defined functions in the same source file.

Note that both the function definition and function declaration for lower are in the prototype style. Although C supports the old style of function declaration in which the parameter types are not specified in the function declarator, it is good programming practice to use prototype declarations for all user-defined functions in your program, and to place the prototypes before the first use of the function. Also note that it is valid for the parameter identifier in the function declaration to be different from the parameter identifier in the function definition.

In a function declaration, the void keyword should be used to specify an empty argument list. For example:


   char function_name(void); 

As with function definitions, the void keyword can also be used in function declarations to specify the return value type for functions that do not return a value. For example:


main() 
{ 
   void function_name( ); 
      . 
      . 
      . 
} 
void function_name( ) 
{ } 

5.5 Function Prototypes

A function prototype is a function declaration that specifies the data types of its arguments in the parameter list. The compiler uses the information in a function prototype to ensure that the corresponding function definition and all corresponding function declarations and calls within the scope of the prototype contain the correct number of arguments or parameters, and that each argument or parameter is of the correct data type.

Prototypes are syntactically distinguished from the old style of function declaration. The two styles can be mixed for any single function, but this is not recommended. The following is a comparison of the old and the prototype styles of declaration:

Old style:

  • Functions can be declared implicitly by their appearance in a call.
  • Arguments to functions undergo the default conversions before the call.
  • The number and type of arguments are not checked.

Note

The HP C compiler will warn about old-style function declarations only in strict ANSI standard mode, or when the check compiler option is specified.

Prototype style:

  • Functions are declared explicitly with a prototype before they are called. Multiple declarations must be compatible; parameter types must agree exactly.
  • Arguments to functions are converted to the declared types of the parameters.
  • The number and type of arguments are checked against the prototype and must agree with or be convertible to the declared types. Empty parameter lists are designated using the void keyword.
  • Ellipses are used in the parameter list of a prototype to indicate that a variable number of parameters are expected.

5.5.1 Prototype Syntax

A function prototype has the following syntax:

function-prototype-declaration:


declaration-specifiersopt declarator;

The declarator includes a parameter type list, which specifies the types of, and can declare identifiers for, the parameters of the function.

A parameter type list can consist of a single parameter of type void to specify that the function has no parameters.

A parameter type list can contain a member that is a variable-length array, specified by the [*] notation.

In its simplest form, a function prototype declaration might have the following format:


storage_classopt return_typeopt function_name ( type1 parameter1, ..., typen parametern );

Consider the following function definition:


char  function_name( int lower, int *upper, char (*func)(), double y ) 
{ } 

The corresponding prototype declaration for this function is:


char  function_name( int lower, int *upper, char (*func)(), double y ); 

A prototype is identical to the header of its corresponding function definition specified in the prototype style, with the addition of a terminating semicolon (;) or comma (,), as appropriate (depending on whether the prototype is declared alone or in a multiple declaration).

Function prototypes need not use the same parameter identifiers as in the corresponding function definition because identifiers in a prototype have scope only within the identifier list. Moreover, the identifiers themselves need not be specified in the prototype declaration; only the types are required.

For example, the following prototype declarations are equivalent:


char  function_name( int lower, int *upper, char (*func)(), double y ); 
char  function_name( int a, int *b, char (*c)(), double d ); 
char  function_name( int, int *, char (*)(), double ); 

Though not required, identifiers should be included in prototypes to improve program clarity and increase the type-checking capability of the compiler.

Variable-length argument lists are specified in function prototypes with ellipses. At least one parameter must precede the ellipses. For example:


char  function_name( int lower, ... ); 

Data-type specifications cannot be omitted from a function prototype.

The C99 standard permits the keyword static to be used within the outermost array bound of a formal parameter in a prototype function declaration. The effect is to assert to the compiler that at each call to the function, the corresponding actual argument will provide access to at least as many array elements as specified in the declared array bound. Consider the following two function definitions:


void foo(int a[1000]){ ... } 
void bar(int b[static 1000]) { ...} 

The declaration of foo is absolutely equivalent to one that declares a to be int * . When compiling the body of foo , the compiler has no information about how many array elements might exist. The declaration of bar differs in that the compiler can assume that at least 1000 array elements exist and may be safely accessed. The intent is to provide a hint to the optimizer about what can be safely pre-fetched.


Previous Next Contents Index