HP C/HP-UX Online Help


Return to the Main HP C Online Help page



Data Types & Declarations

Program Structure
Declarations
Storage-Class Specifiers
Type Specifiers
HP-Specific Type Qualifiers
Type Qualifiers
Structure and Union Specifiers
Enumeration
Declarators
Type Names
Type Definitions Using typedef
Initialization
Function Definitions
Four-Byte Extended UNIX Code (EUC)

In C, as in many other programming languages, you must usually declare identifiers before you can use them.

The declarable entities in C are:

This section describes declarations, type specifiers, storage-class specifiers, structure and union specifiers, enumerations, declarators, type names, and initialization. Data types and declarations are defined using Backus-Naur form.

Program Structure

A translation unit consists of one or more declarations and function definitions.

Syntax

translation-unit ::=
   external-declaration
   translation-unit
external-declaration
 
external-declaration ::=
   function-definition
   declaration

Description

A C program consists of one or more translation units, each of which can be compiled separately. A translation unit consists of a source file together with any headers and source files included by the #include preprocessing directive. Each time the compiler is invoked, it reads a single translation unit and typically produces a relocatable object file. A translation unit must contain at least one declaration or function definition.

Declarations

A declaration specifies the attributes of an identifier or a set of identifiers.

Syntax

declaration ::=
    declaration-specifiers
[init-declarator-list] ;
 
declaration-specifiers ::=
     storage-class-specifier
[declaration-specifiers]
     type-specifier
[declaration-specifiers]
     type-qualifier
[declaration-specifiers]
 
init-declarator-list ::=
     init-declarator
     init-declarator-list , init-declarator
 
init-declarator ::=
     declarator
     declarator = initializer

Description

Making a declaration does not necessarily reserve storage for the identifiers declared. For example, the declaration of an external data object provides the compiler with the attributes of the object, but the actual storage is allocated in another translation unit.

A declaration consists of a sequence of specifiers that indicate the linkage, storage duration, and the type of the entities that the declarators denote.

You can declare and initialize objects at the same time using the init-declarator-list syntax. The init-declarator-list is a comma-separated sequence of declarators, each of which may have an initializer.

Function definitions have a slightly different syntax as discussed in Function Declarators . Also, note that it is often valid to define a tag (struct, union, or enum) without actually declaring any objects.

New Declaration Feature

HP C has added the C9x feature which allows you to declare variables and types inside a block of statements. This also allows declaration of new variables or types, such as expr_1, as shown in the for statement below:
for(expr_1;expr_2;expr_3) statement_1

This new variable or type declared in expr_1 can be used in expr_2, expr_3 and statement_1.

Caveats

The HP C/ANSI C compiler implementation of declarations within code is similar to, but not identical to, the C++ implementation of declarations within code. When specifying declarations within code in the HP C/ANSI C compiler, do not expect the same behavior in HP aC++. For example:

for(int i = 0; i < j; i ++) int i;

Note the lack of a new block opening for the for statement. The C++ compiler accepts this form, with warnings, but the C compiler does not. The difference in the way the stack is handled causes the difference in behavior.

Previously, the C compiler did not emit the source file information for the global typedefs. To correct this, use -y option along with -g when debug info is generated. You can generate debug information by compiling with +Oobjdebug.

Example

int main()
{
  int i=5,j;

  j=i*i;
  printf(*"%d\n",j);

  int k=j;
  /*This is accepted in the new release of HP C*/

for(struct aa {int a;int b} AA={10,50};AA.a<=AA.b;AA.a++){
     /*This is accepted by the new feature */
     printf("%d\n",AA.a);}
}

Examples

Valid Declarations:
extern int pressure [ ];            /* size will be declared elsewhere  */
extern int lines = 66, pages;       /* declares two variables,
                                       initializes the first one        */
static char private_func (float);   /* a function taking a float,
                                       returning a char, not known
                                       outside this unit                */
const float pi = 3.14;              /* a constant float, initialized    */
 
const float * const pi_ptr = &pi;   /* a constant pointer to a constant
                                       float, initialized with an
                                       address constant                 */
static j1, j2, j3;                  /* initialized to zero by default   */
typedef struct
   {double real, imaginary;} Complex; /* declares a type name           */
Complex impedance = {47000};          /* second member defaults to zero */
enum color {red=1, green, blue};      /* declares an enumeration tag and
                                         three constants                */
int const short static volatile signed
   really_Strange = {sizeof "\?"};    /* pretty mixed up                */
Invalid Declarations:
int ;                               /* no identifier */
;                                   /* no identifier */
int i; j;                          /* no specifiers for j */

Storage-Class Specifiers

A storage-class specifier is one of several keywords that determines the duration and linkage of an object.

Syntax

storage-class ::=
      typedef
      extern
      static
      auto
      register

Description

You can use only one storage-class specifier in a declaration.

The typedef keyword is listed as a storage-class specifier because it is syntactically similar to one.

The keyword extern affects the linkage of a function or object name. If the name has already been declared in a declaration with file scope, the linkage will be the same as in that previous declaration. Otherwise, the name will have external linkage.

The static storage-class specifier may appear in declarations of functions or data objects. If used in an external declaration (either a function or a data object), static indicates that the name cannot be referenced by other translation units. Using the static storage class in this way allows translation units to have collections of local functions and data objects that are not exported to other translation units at link time.

If the static storage class is used in a declaration within a function, the value of the variable is preserved between invocations of that function.

The auto storage-class specifier is permitted only in the declarations of objects within blocks. An automatic variable is one that exists only while its enclosing block is being executed. Variables declared with the auto storage-class are all allocated when a function is entered. Auto variables that have initializes are initialized when their defining block is entered normally. This means that auto variables with initializes are not initialized when their declaring block is not entered through the top.

The register storage class suggests that the compiler store the variable in a register, if possible. You cannot apply the & (address-of) operator to register variables.

If no storage class is specified and the declaration appears in a block, the compiler defaults the storage duration for an object to automatic. If the declaration of an identifier for a function has no storage-class specifier, its linkage is determined exactly as if it were declared with the extern storage-class specifier.

If no storage class is specified and the declaration appears outside of a function, the compiler treats it as an externally visible object with static duration.

Type Specifiers

Type specifiers indicate the format of the storage associated with a given data object or the return type of a function.

Syntax

   type-specifier ::=
       char
       short
       int
       long
       long long
       unsigned
       signed
       float
       double
       void
       struct-or-union-specifier
       enum-specifier
       typedef-name

Description

Most of the type specifiers are single keywords. The syntax of the type specifiers permits more types than are actually allowed in the C language. The various combinations of type specifiers that are allowed are shown in Table 13: C Type Specifiers . Type specifiers that are equivalent appear together in a box. For example, specifying unsigned is equivalent to unsigned int. Type specifiers may appear in any order, possibly intermixed with other declaration specifiers.
 

Table 13: C Type Specifiers 
void
char
signed char
unsigned char
short, signed short, short int, or signed short int
unsigned short, or unsigned short int
int, signed, signed int, or no type specifiers
unsigned, or unsigned int
long, signed long, long int, or signed long int
long long, signed long long, long long int, or signed long long int
unsigned long, or unsigned long int
unsigned long long, or unsigned long long int
float
double
long double
struct-or-union specifier
enum-specifier
typedef-name

If no type specifier is provided in a declaration, the default type is int.

Floating-point types in C are:


HP-Specific Type Qualifiers

Syntax

type-qualifier ::= __thread

Description

The __thread keyword defines a thread-specific data variable, distinguishing it from other data items that are shared by all threads. With a thread-specific data variable, each thread has its own copy of the data item. These variables eliminate the need to allocate thread-specific data dynamically, thus improving performance.

This keyword is implemented as an HP specific type qualifier, with the same syntax as type qualifiers const and volatile, but not the same semantics.

Syntax examples:

__thread int var;

int __thread var;

Semantics: Only variables of static duration can be thread-specific. Thread-specific data objects can not be initialized. Pointers of static duration that are not thread-specific may not be initialized with the address of a thread-specific object - assignment is allowed. All global variables, thread-specific or not, are initialized to zero by the linker implicitly.

Only one declaration, for example,

__thread int x;

is allowed in one compilation unit that contributes to the program (including libraries linked into the executable). All other declarations must be strictly references:

extern __thread int x;

Any other redeclarations of this thread-specific x will result in a duplicate definition error at link time.

Even though __thread has the same syntax as a type qualifier, it does not qualify the type, but is a storage class specification for the data object. As such, it is type compatible with non-thread-specific data objects of the same type. That is, a thread-specific data int is type compatible with an ordinary int, (unlike const and volatile qualified int).


NOTE  Use of the __thread keyword in a shared library will prevent that shared library from being dynamically loaded (that is, loaded via an explicit call to shl_load.

Type Qualifiers

Syntax

type-qualifier ::= const
                volatile

Description

This section describes the type qualifiers - volatile and const.

The volatile type qualifier directs the compiler not to perform certain optimizations on an object because that object can have its value altered in ways beyond the control of the compiler.

Specifically, when an object"s declaration includes the volatile type qualifier, optimizations that would delay any references to (or modifications of) the object will not occur across sequence points. A sequence point is a point in the execution process when the evaluation of an expression is complete, and all side-effects of previous evaluations have occurred.

The volatile type qualifier is useful for controlling access to memory-mapped device registers, as well as for providing reliable access to memory locations used by asynchronous processes.

The const type qualifier informs the compiler that the object will not be modified, thereby increasing the optimization opportunities available to the compiler.

An assignment cannot be made to a constant pointer, but an assignment can be made to the object to which it points. An assignment can be made to a pointer to constant data, but not to the object to which it points. In the case of a constant pointer to constant data, an assignment cannot be made to either the pointer, or the object to which it points.

Type qualifiers may be used alone (as the sole declaration-specifier), or in conjunction with type specifiers, including struct, union, enum, and typedef. Type qualifiers may also be used in conjunction with storage-class specifiers.

Table 14 illustrates various declarations using the const and volatile type qualifiers.
 
Table 14: Declarations using const and volatile 
Declaration  Meaning 
volatile int vol_int; Declares a volatile int variable.
const int *ptr_to_const_int;

int const *ptr_to_const_int;

Both declare a variable pointer to a constant int.
int *const const_ptr_to_int Declares a constant pointer to a variable int.
int *volatile vpi, *pi; Declares two pointers: vpi is a volatile pointer to an int; pi is a pointer to an int.
int const *volatile vpci; Declares a volatile pointer to a constant int.
const *pci; Declares a pointer to a constant int. Since no type specifier was given, it defaults to int.

When a type qualifier is used with a variable typed by a typedef name, the qualifier is applied without regard to the contents of the typedef. For example:

typedef int *t_ptr_to_int;
volatile t_ptr_to_int vol_ptr_to_int;
In the example above, the type of vol_ptr_to_int is volatile t_ptr_to_int, which becomes volatile pointer to int. If the type t_ptr_to_int were substituted directly in the declaration,
volatile int * ptr_to_vol_int;
the type would be pointer to volatile int.

Type qualifiers apply to objects, not to types. For example:

typedef int * t;
const t *volatile p;
In the example above, p is a volatile pointer to a const pointer to int. volatile applies to the object p, while const applies to the object pointed to by p. The declaration of p can also be written as follows:
t const *volatile p;
If an aggregate variable such as a structure is declared volatile, all members of the aggregate are also volatile.

If a pointer to a volatile object is converted to a pointer to a non-volatile type, and the object is referenced by the converted pointer, the behavior is undefined.

Structure and Union Specifiers

A structure specifier indicates an aggregate type consisting of a sequence of named members. A unionspecifier defines a type whose members begin at offset zero from the beginning of the union.

Syntax

struct-or-union
specifier ::=
    struct-or-union
[identifier] { struct-declaration-list }
    struct-or-union
identifier
 
struct-or-union ::=
     struct
     union
 
struct-declaration-list ::=
     struct-declaration
     struct-declaration-list
struct-declaration
 
struct-declaration ::=
     specifier-qualifier-list
struct-declarator-list;
 
specifier-qualifier-list ::=
     type-specifier
[specifier-qualifier-list]
     type-qualifier
[specifier-qualifier-list]
 
struct-declarator-list ::=
     struct-declarator
     struct-declarator-list
, struct-declarator
 
struct-declarator ::=
     declarator
     [declarator]
: constant-expression

Description

A structure is a named collection of members. Each member belongs to a name space associated with the structure. Members in different structures can have the same names but represent different objects.

Members are placed in physical storage in the same order as they are declared in the definition of the structure. A member"s offset is the distance from the start of the structure to the beginning of the member. The compiler inserts pad bytes as necessary to insure that members are properly aligned. For example, if a char member is followed by a float member, one or more pad bytes may be inserted to insure that the float member begins on an appropriate boundary.

See Data Storage and Alignment for more information.

Unions are like structures except that all members of a union have a zero offset from the beginning of the union. In other words, the members overlap. Unions are a way to store different type of objects in the same memory location.

A declarator for a member of a structure or union may occupy a specified number of bits. This is done by following the declarator with a colon and a constant non-negative integral expression. The value of the expression indicates the number of bits to be used to hold the member. This type of member is called a bit-field. Only integral type specifiers are allowed for bit-field declarators.

In structures, bit-fields are placed into storage locations from the most significant bits to the least significant bits. Bit-fields that follow one another are packed into the same storage words, if possible. If a bit-field will not fit into the current storage location, it is put into the beginning of the next location and the current location is padded with an unnamed field.

A colon followed by an integer constant expression indicates that the compiler should create an unnamed bit-field at that location. In addition, a colon followed by a zero indicates that the current location is full and that the next bit-field should begin at the start of the next storage location.

Although bit-fields are permitted in unions (ANSI mode only), they are just like any other members of the union in that they have a zero offset from the beginning of the union. That is, they are not packed into the same word, as in the case of structures. The special cases of unnamed bit-fields and unnamed bit-fields of length zero behave differently with unions; they are simply unnamed members that cannot be assigned to.

The unary address operator (&) may not be applied to bit-fields. This implies that there cannot be pointers to bit-fields nor can there be arrays of bit-fields.

Refer to Chapter 10, "HP C/HP-UX Implementation Topics," in the HP C/HP-UX Reference Manual for more information on bit-fields.

Structure and Union Tags

Structures and unions are declared with the struct or union keyword. You can follow the keywords with a tag that names the structure or union type much the same as an enum tag names the enumerated type. (Refer to Enumeration for information on enumerated types.) Then you can use the tag with the struct or union keyword to declare variables of that type without re-specifying member declarations. A structure tag occupies a separate name space reserved for tags. Thus, a structure tag may have the same spelling as a structure member or an ordinary identifier. Structure tags also obey the normal block scope associated with identifiers. Another tag of the same spelling in a subordinate block may hide a structure tag in an outer block.

A struct or union declaration has two parts: the structure body, where the members of the structure are declared (and possibly a tag name associated with them); and a list of declarators (objects with the type of the structure).

Either part of the declaration can be empty. Thus, you can put the structure body declaration in one place, and use the struct type in another place to declare objects of that type.

For example, consider the following declarations:

struct s1 {
     int x;
     float y;
};
 
struct s1 obj1, *obj2;
The first example declares only the struct body and its associated tag name. The second example uses the struct tag to declare two objects - obj1 and obj2. They are, respectively, a structure object of type struct s1 and a pointer object, pointing to an object of type struct s1.

This allows you to separate all the struct body declarations into one place (for example, a header file) and use the struct types elsewhere in the program when declaring objects.

Consider the following example:

struct examp {
   float f;         /* floating member */
   int   i;         /* integer member */
};                  /* no declaration list */
In this example, the structure tag is examp and it is associated with the structure body that contains a single floating-point quantity and an integer quantity. Note that no objects are declared after the definition of the structure"s body; only the tag is being defined.

A subsequent declaration may use the defined structure tag:

  struct examp x, y[100];
This example defines two objects using type struct examp. The first is a single structure named x and the second, y, is an array of structures of type struct examp.

Another use for structure tags is to write self-referential structures. A structure of type S may contain a pointer to a structure of type S as one of its members. Note that a structure can never have itself as a member because the definition of the structure"s content would be recursive. A pointer to a structure is of fixed size, so it may be a member. Structures that contain pointers to themselves are key to most interesting data structures. For example, the following is the definition of a structure that is the node of a binary tree:

struct node {
   float data;            /* data stored at the node */
   struct node *left;     /* left subtree */
   struct node *right;    /* right subtree */
 };
This example defines the shape of a node type of structure. Note that the definition contains two members (left and right) that are themselves pointers to structures of type node.

The C programming rule that all objects must be defined before use is relaxed somewhat for structure tags. A structure can contain a member that is a pointer to an as yet undefined structure. This allows for mutually referential structures:

struct s1 { struct s2 *s2p; };
struct s2 { struct s1 *s1p; };
In this example, structure s1 references the structure tag s2. When s1 is declared, s2 is undefined. This is valid.

Example

struct tag1 {
   int m1;
   int   :16;    /* unnamed bit-field */
   int m2:16;    /* named bit-field; packed into */
                 /* same word as previous member */
   int m3, m4;
};               /* empty declarator list */
union tag2 {
   int u1;
   int   :16;
   int u2:16;   /* bit-field, starts at offset 0 */
   int u3, u4;
} fudge1, fudge2;   /* declarators denoting objects
                       of the union type           */
 
struct tag1 obj1, *obj2;  /* use of type "struct tag1",
                      whose body has been declared above */

Enumeration

The identifiers in an enumeration list are declared as constants.

Syntax

enum-specifier ::=
   [ type-specifier ] enum
[ identifier ] {enumerator-list}
   [ type-specifier ] enum
identifier
 
enumerator-list ::=
   enumerator
   enumerator-list
, enumerator
 
enumerator ::=
   enumeration-constant
   enumeration-constant
= constant-expression
 
enumeration-constant ::= identifier

Description

The identifiers defined in the enumerator list are enumeration constants of type int. As constants, they can appear wherever integer constants are expected. A specific integer value is associated with an enumeration constant by following the constant with an equal sign ( = ) and a constant expression. If you define the constants without using the equal sign, the first constant will have the value of zero and the second will have the value of one, and so on. If an enumerator is defined with the equal sign followed by a constant expression, that identifier will take on the value specified by the expression. Subsequent identifiers appearing without the equal sign will have values that increase by one for each constant. For example,
enum color {red, blue, green=5, violet};
defines red as 0, blue as 1, green as 5, and violet as 6.

Enumeration constants share the same name space as ordinary identifiers. They have the same scope as the scope of the enumeration in which they are defined. You can also use the int or long type specifier to indicate 4-byte enums, even though 4-byte enums are the default.

The identifier in the enum declaration behaves like the tags used in structure and union declarations. If the tag has already been declared, you can use the tag as a reference to that enumerated type later in the program.

enum color x, y[100];
In this example, the color enumeration tag declares two objects. The x object is a scalar enum object, while y is an array of 100 enums.

An enumeration tag cannot be used before its enumerators are declared.

Examples

enum color {RED, GREEN, BLUE};
 
enum objectkind {triangle, square=5, circle};  /* circle == 6 */

Sized enum - HP C Extension

By default, the HP C compiler on HP 9000 systems allocates four bytes for all enumerated variables. However, if you know that the range of values being assigned to an enum variable is small, you can direct the compiler to allocate only one or two bytes by using the char or short type specifier. If the range is large, you can direct the compiler to allocate eight bytes by using the long long type specifier. You can also use the long type specifier to indicate 4-byte enums, even though this is the default. For example:
long long enum bigger_enum {barge, yacht}; /* 8-byte enum type */
enum default_enum {ERR1, ERR2, ERR3, ERR4};/* 4-byte enum type */
long enum big_enum {STO, ST1, ST2, ST3};   /* 4-byte enum type */
short enum small_enum {cats, dogs};        /* 2-byte enum type */
char enum tiny_enum {alpha, beta};         /* 1-byte enum type */
When mixed in expressions, enums behave exactly as their similarly sized type counterparts do. That is, an enum behaves like an int, a long enum acts like a long int, and a short enum acts like a short int. You will, however, receive a warning message when you mix enum variables with integer or floating-point types, or with differently typed enums.

The sizeof() function returns the actual storage allocated when called with enum-specifier.


NOTE  enumeration-constants will have the same size as the type specified in the enumeration declaration.
char enum {a};  /* sizeof(a) returns 1. */

Declarators

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:

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

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.

Type Names

A type name is syntactically a declaration of an object or a function of a given type that omits the identifier. Type names are often used in cast expressions and as operands of the sizeof operator.

Syntax

type-name ::=
    specifier-qualifier-list
[abstract-declarator]
 
abstract-declarator ::=
    pointer
    [pointer]
direct-abstract-declarator
 
direct-abstract-declarator
    (abstract-declarator )
    [direct-abstract-declarator] [ [constant-expression] ]
    [direct-abstract-declarator] ( [parameter-type-list] )

Description

Type names are enclosed in parentheses to indicate a cast operation. The destination type is the type named in the cast; the operand is then converted to that type.

A type name is a declaration without the identifier specified. For example, the declaration for an integer is int i. If the identifier is omitted, only the integer type int remains.

Examples

     int             int
     int *           Pointer
to int
     int ()          Function
returning an int
     int *()         Function
returning a pointer to int
     int (*)()       Pointer
to function returning an int
     int [3];        Array
of 3 int
     int *[3];       Array
of 3 pointers to int
     int (*)[3];     Pointer
to an array of 3 int
The parentheses are necessary to alter the binding order in the cases of pointer to function and pointer to array. This is because function and array declarators have higher precedence than the pointer declarator.

Type Definitions Using typedef

The typedef keyword, useful for abbreviating long declarations, allows you to create synonyms for C data types and data type definitions.

Syntax

typedef-name ::= identifier

Description

If you use the storage class typedef to declare an identifier, the identifier is a name for the declared type rather than an object of that type. Using typedef does not define any objects or storage. The use of a typedef does not actually introduce a new type, but instead introduces a synonym for a type that already exists. You can use typedef to isolate machine dependencies and thus make your programs more portable from one operating system to another.

For example, the following typedef defines a new name for a pointer to an int:

  typedef int *pointer;
Instead of the identifier pointer actually being a pointer to an int, it becomes the name for the pointer to the int type. You can use the new name as you would use any other type. For example:
  pointer p, *ppi;
This declares p as a pointer to an int and ppi as a pointer to a pointer to an int.

One of the most useful applications of typedef is in the definition of structure types. For example:

  typedef struct {
   float real;
   float imaginary;
 } complex;
The new type complex is now defined. It is a structure with two members, both of which are floating-point numbers. You can now use the complex type to declare other objects:
  complex x, *y, a[100];
This declares x as a complex, y as a pointer to the complex type and a as an array of 100 complex numbers. Note that functions would have to be written to perform complex arithmetic because the definition of the complex type does not alter the operators in C.

Other type specifiers (that is, void, char, short, int, long, long long, signed, unsigned, float, or double) cannot be used with a name declared by typedef. For example, the following typedef usage is illegal:

typedef long int li;
.
.
.
unsigned li x;
typedef identifiers occupy the same name space as ordinary identifiers and follow the same scoping rules.

Structure definitions which are used in typedef declarations can also have structure tags. These are still necessary to have self-referential structures and mutually referential structures.

Example

typedef unsigned long ULONG;  /* ULONG is an unsigned long */
typedef int (*PFI)(int);  /* PFI is a pointer to a function */
     /* taking an int and returning an int */
 
ULONG v1; /* equivalent to "unsigned long v1" */
PFI v2;  /* equivalent to "int (*v2)(int)"   */

Initialization

An initializer is the part of a declaration that provides the initial values for the objects being declared.

Syntax

initializer ::=
    assignment-expression
    {initializer-list}
    {initializer-list , }
 
initializer-list ::=
    initializer
    initializer-list
, initializer

Description

A declarator may include an initializer that specifies the initial value for the object whose identifier is being declared.

Objects with static storage duration are initialized at load time. Objects with automatic storage duration are initialized at run-time when entering the block that contains the definition of the object. An initialization of such an object is similar to an assignment statement.

You can initialize a static object with a constant expression. You can initialize a static pointer with the address of any previously declared object of the appropriate type plus or minus a constant.

You can initialize an auto scalar object with an expression. The expression is evaluated at run-time, and the resulting value is used to initialize the object.

When initializing a scalar type, you may optionally enclose the initializer in braces. However, they are normally omitted. For example

int i = {3};
is normally specified as
int i = 3;
When initializing the members of an aggregate, the initializer is a brace-enclosed list of initializes. In the case of a structure with automatic storage duration, the initializer may be a single expression returning a type compatible with the structure. If the aggregate contains members that are aggregates, this rule applies recursively, with the following exceptions: In ANSI mode, the initializer lists are parsed "top-down;" in compatibility mode, they are parsed "bottom-up." For example,
int q [3] [3] [2] = {
       { 1 }
       { 2, 3 }
       { 4, 5, 6 }
};
produces the following layout:
ANSI Mode            Compatibility Mode
1 0 0 0 0 0             1 0 2 3 4 5
2 3 0 0 0 0             6 0 0 0 0 0
4 5 6 0 0 0             0 0 0 0 0 0
It is advisable to either fully specify the braces, or fully elide all but the outermost braces, both for readability and ease of migration from compatibility mode to ANSI mode.

Because the compiler counts the number of specified initializes, you do not need to specify the size in array declarations. The compiler counts the initializes and that becomes the size:

  int x[ ] = {1, 10, 30, 2, 45};
This declaration allocates an array of int called x with a size of five. The size is not specified in the square brackets; instead, the compiler infers it by counting the initializes.

As a special case, you can initialize an array of characters with a character string literal. If the dimension of the array of characters is not provided, the compiler counts the number of characters in the string literal to determine the size of the array. Note that the terminating \0 is also counted. For example:

  char message[ ] = "hello";
This example defines an array of characters named message that contains six characters. It is identical to the following:
  char message[ ] = {"h","e","l","l","o","\0"};
You can also initialize a pointer to characters with a string literal:
  char *cp = "hello";
This declares the object cp as a character pointer initialized to point to the first character of the string "hello".

It is illegal to specify more initializes in a list than are required to initialize the specified aggregate. The one exception to this rule is the initialization of an array of characters with a string literal.

  char t[3] = "cat";
This initializes the array t to contain the characters c, a, and t. The trailing "\0" character is ignored.

If there are not enough initializes, the remainder of the aggregate is initialized to zero.

Some more examples include:

  char *errors[ ] = {
   "undefined file",
   "input error",
   "invalid user"
};
In this example, the array errors is an array of pointers to character (strings). The array is initialized with the starting addresses of three strings, which will be interpreted as error messages.

An array with element type compatible with wchar_t (unsigned int) may be initialized by a wide string literal, optionally enclosed in braces. Successive characters of the wide string literal initialize the members of the array. This includes the terminating zero-valued character, if there is room or if the array is of unknown size.

Examples

wchar_t wide_message[ ]=L"x$$z";
You initialize structures as you do any other aggregate:
  struct{
   int i;
   unsigned u:3;
   unsigned v:5;
   float f;
   char *p;
} s[ ] = {
    {1, 07, 03, 3.5, "cats eat bats" },
    {2,  2,  4, 5.0, "she said with a smile"}
};
Note that the object being declared (s) is an array of structures without a specified dimension. The compiler counts the initializes to determine the array"s dimension. In this case, the presence of two initializes implies that the dimension of s is two. You can initialize named bit-fields as you would any other member of the structure.

If the value used to initialize a bit-field is too large, it is truncated to fit in the bit-field.

For example, if the value 11 were used to initialize the 3-bit field u above, the actual value of u would be 3 (the top bit is discarded).

A struct or union with automatic storage duration can also be initialized with a single expression of the correct type.

struct SS { int y; };
extern struct SS g(void);
func()
{
   struct SS z = g();
}
When initializing a union, since only one union member can be active at one time, the first member of the union is taken to be the initialized member.

The union initialization is only available in ANSI mode.

union {
   int        i;
   float      f;
   unsigned u:5;
} = { 15 };

Function Definitions

A function definition introduces a new function.

Syntax

function-definition ::=
[declaration-specifiers]
declarator [declaration-list] compound-statement

Description

A function definition provides the following information about the function:

NOTE  Function prototypes can be used only in ANSI mode.

Visibility outside defining translation unit. A function can be local to the translation unit in which it is defined (if the storage class specifier is static). Alternatively, a function can be visible to other translation units (if no storage class is specified, or if the storage class is extern).

Body of the function. You supply the body that executes when the function is called in a single compound statement following the optional declaration-list. Do not confuse definition with declaration, especially in the case of functions. Function definition implies that the above four pieces of information are supplied. Function declaration implies that the function is defined elsewhere.

You can declare formal parameters as structures or unions. When the function is called, the calling function"s argument is copied to temporary locations within the called function.

All functions in C may be recursive. They may be directly recursive so the function calls itself or they may be indirectly recursive so a function calls one or more functions which then call the original function. Indirect recursion can extend through any number of layers.

In function definitions that do not use prototypes, any parameters of type float are actually passed as double, even though they are seen by the body of the function as floats. When such a function is called with a float argument, the float is converted back to float on entry into the function.


NOTE  In compatibility mode, the type of the parameter is silently changed to double, so the reverse conversion does not take place.

In a prototype-style definition, such conversions do not take place, and the float is both passed and accessed in the body as a float.

char and short parameters to nonprototype-style function definitions are always converted to type int. This conversion does not take place in prototype-style definitions.

In either case, arrays of type T are always adjusted to pointer to type T, and functions are adjusted to pointers to functions.

Single dimensioned arrays declared as formal parameters need not have their size specified. If the name of an integer array is x, the declaration is as follows:

int x[ ];
For multidimensional arrays, each dimension must be indicated by a pair of brackets. The size of the first dimension may be left unspecified.

The storage class of formal parameters is implicitly "function parameter." A further storage class of register is accepted.

Examples

The following example shows a function that returns the sum of an array of integers.
int total(data, n) /* function type, name, formal list */
int data[ ];       /* parameter declarations */
int n;
{
    auto int sum = 0;   /* local, initialized */
    auto int i;         /* loop variable */
 
    for(i=0; i<n; ++i)  /* range over all elements */
       sum += data[i];  /* total the data array */
    return sum;         /* return the value */
}
This is an example of a function definition without prototypes.
int func1 (p1, p2)       /* old-style function definition */
int p1, p2;              /* parameter declarations */
{                        /* function body starts */
    int l1;              /* local variables */
    l1 = p1 + p2;
    return l1;
}
Here is an example of a function definition using prototypes.
char *func2 (void)          /* new-style definition */
                            /* takes no parameters  */
{
    /* body */
}
 
int func3 (int p1, char *p2, ...)/* two declared parameters:
                                    p1 & p2 */
                                 /* "..." specifies more,
                                    undeclared parameters
                                    of unspecified type   */
{
    /* body */                   /* to access undeclared
                                    parameters here, use the
                                    functions declared in the
                                    <stdarg.h> header file.  */
}

Four-Byte Extended UNIX Code (EUC)

HP C/HP-UX supports four-byte Extended UNIX Code (EUC) characters in filenames, comments, and string literals.

sizeof

See sizeof Operator .

static

A variable that has memory allocated for it at program startup time. The variable is associated with a single memory location until the end of the program.

struct

See Structure and Union Tags .

switch

See switch .

__thread

Beginning with the HP-UX 10.30 operating system release, this HP-specific keyword defines a thread specific data variable, distinguishing it from other data items that are shared by all threads. With a thread-specific data variable, each thread has its own copy of the data item. These variables eliminate the need to allocate thread-specific data dynamically, thus improving performance.

This keyword is implemented as an HP-specific type qualifier, with the same syntax as const and volatile, but not the same semantics. Syntax examples:

__thread int var;
int __thread var;
Semantics for the __thread keyword: Only variables of static duration can be thread specific. Thread specific data objects can not be initialized. Pointers of static duration that are not thread specific may not be initialized with the address of a thread specific object - assignment is okay. All global variables, thread specific or not, are initialized to zero by the linker implicitly.

Only one declaration, for example,

__thread int x;
is allowed in one compilation unit that contributes to the program (including libraries linked into the executable). All other declarations must be strictly references:
extern __thread int x;
Even though __thread has the same syntax as a type qualifier, it does not qualify the type, but is a storage class specification for the data object. As such, it is type compatible with non-thread-specific data objects of the same type. That is, a thread specific data int is type compatible with an ordinary int, (unlike const and volatile qualified int).

Note that use of the __thread keyword in a shared library will prevent that shared library from being dynamically loaded (that is, loaded via an explicit call to shl_load()).

typedef

See Typedef Declarations.

union

See Structure and Union Tags .

unsigned

A data type modifier that indicates that no sign bit will be used. The data is assumed to contain values greater than or equal to zero. All integer data types are signed by default. The unsigned keyword can be used to modify these data types:

void

The void data type has three important purposes: To indicate that a function does not return a value, you can write a function definition such as:
void func(int a, int b)
{
      . . .    
}
This indicates that the function func() does not return a value. Likewise, on the calling side, you declare func() as:
extern void func(int, int);

volatile

Specifies that the value of a variable might change in ways that the compiler cannot predict. If volatile is used, the compiler will not perform certain optimizations on that variable.

while

See while .

Declarations

The following declarations are described in this section:

In general, a variable declaration has the following format:

[storage_class_specifier] [data_type] variable_name
    [=initial_value];
where: Here are a few sample variable declarations without storage class specifiers or initial values:
int   age;                 /* integer variable "age" */
int length, width;         /* abbreviated declaration of two
                              variables*/
float ph;                  /* floating-point variable "ph" */
char  a_letter;            /* character variable "a_letter" */
int   values[10];         /* array of 10 integers named values */
enum  days {mon, wed, fri}; /* enumerated variable "days" */

Typedef Declarations

The C language allows you to create your own names for data types with the typedef keyword. Syntactically, a typedef is similar to a variable declaration except that the declaration is preceded by the typedef keyword.

A typedef declaration may appear anywhere a variable declaration may appear and obeys the same scoping rules as a normal declaration. Once declared, a typedef name may be used anywhere that the type is allowed (such as in a declaration, cast operation, or sizeof operation). You can write typedef names in all uppercase so that they are not confused with variable names.

You may not include an initializer with a typedef.

The statement:

typedef long int FOUR_BYTE_INT;
makes the name FOUR_BYTE_INT synonymous with long int. The following two declarations are now identical:
long int j;
FOUR_BYTE_INT j;

Abstract Global Types

Typedefs are useful for abstracting global types that can be used throughout a program, as shown in the following structure and array declaration:
typedef struct {
    char  month[4];
    int   day;
    int   year;
} BIRTHDAY;
 
typedef char A_LINE[80]; /* A_LINE is an array of
                          * 80 characters */

Improving Portability

Type definitions can be used to compensate for differences in C compilers. For example:
#if SMALL_COMPUTER
    typedef int SHORTINT;
    typedef long LONGINT;

#elif
    BIG_COMPUTER
    typedef short SHORTINT;
    typedef int LONGINT;

#endif
This is useful when writing code to run on two computers, a small computer where an int is two bytes, and a large computer where an int is four bytes. Instead of using short, long, and int, you can use SHORTINT and LONGINT and be assured that SHORTINT is two bytes and LONGINT is four bytes regardless of the machine.

Simplifying Complex Declarations

You can use typedefs to simplify complex declarations. For example:
typedef float *PTRF, ARRAYF[], FUNCF();
This declares three new types called PTRF (a pointer to a float), ARRAYF (an array of floats), and FUNCF (a function returning a float). These typedefs could then be used in declarations such as the following:
PTRF x[5];  /* a 5-element array of pointers to floats */
FUNCF z;    /* A function returning a float */

Using typedefs for Arrays

The following two examples illustrate what can happen when you mix pointers and typedefs that represent arrays. The problem with the program on the left is that ptr points to an array of 80 chars, rather than a single element of a char array. Because of scaling in pointer arithmetic, the increment operator adds 80 bytes, not one byte, to ptr.
 
wrong right
typedef char STR[80];      
STR   string, *ptr;         
                           
                           
                            
main()                      
{                           
   ptr = string;            
   printf("ptr = %d\n", ptr);
   ptr++;                     
   printf("ptr = %d\n", ptr);  
}                            
                            
*** Run-Time Results ***     
                             
ptr = 3997696                
ptr = 3997776                
typedef char STR[80];      
STR   string;             
char  *ptr;                
                            
                            
main()                      
{                            
   ptr = string;
   printf("ptr = %d\n", ptr);
   ptr++;
   printf("ptr = %d\n", ptr);
}                            
                           
*** Run-Time Results ***     
                            
ptr = 3997696               
ptr = 3997697               

Name Spaces

All identifiers (names) in a program fall into one of four name spaces. Names in different name spaces never interfere with each other. That is, you can use the same name for an object in each of the four name spaces without these names affecting one another. The four name spaces are as follows:
NOTE  The separate name spaces for goto labels and for each struct, union, or enum definition are part of the ANSI/ISO standard, but not part of the K&R language definition.

The following example uses the same name, overuse, in four different ways:

int main(void)
{
    int overuse;       /* normal identifier */
    struct overuse {   /* tag name */
        float overuse; /* member name */
        char *p;
    } x;
    goto overuse;
overuse: overuse = 3;  /* label name */
}

Structure, Union, and Enum Names

Each struct, union, or enum defines its own name space, so that different declarations can have the same member names without conflict. The following is legal:
struct A {
    int x;
    float y;
};
struct B {
    int x;
    float y;
};
The members in struct A are distinct from the members in structB.

Macro Names

Macro names do interfere with the other four name spaces. Therefore, when you specify a macro name, do not use this name in one of the other four name spaces. For example, the following program fragment is incorrect because it contains a macro named square and a label named square:
# define square(arg)  arg * arg
 
int main(void)
{
  ...
square: 
  ...
}

Constants

There are four types of constants in C:
Every constant has two properties: value and type. For example, the constant 15 has value 15 and type int.

Integer Constants

HP C supports three forms of integer constants:

Integer constants may not contain any punctuation such as commas or periods.

Examples of Integer Constants

The following examples show some legal constants in decimal, octal, and hexadecimal form:
 
Decimal  Octal  Hexadecimal 
3 003 0x3
8 010 0x8
15 017 0xF
16 020 0x10
21 025 0x15
-87 -0127 -0x57
187 0273 0xBB
255 0377 0xff

Floating-Point Constants

A floating-point constant is any number that contains a decimal point and/or exponent sign for scientific notation.

The number may be followed by an f or F, to signify that it is of type float, or by an l or L, to signify that it is of type long double. If the number does not have a suffix, it is of type double even if it can be accurately represented in four bytes.

If the magnitude of a floating-point constant is too great or too small to be represented in a double, the C compiler will substitute a value that can be represented. This substitute value is not always predictable.

You may precede a floating-point constant with the unary plus or minus operator to make its value positive or negative.

Scientific Notation

Scientific notation is a useful shorthand for writing lengthy floating-point values. In scientific notation, a value consists of two parts: a number called the mantissa followed by a power of 10 called the characteristic (or exponent).

The letter e or E, standing for exponent, is used to separate the two parts.

The floating-point constant 3e2, for instance, is interpreted as 3*(102), or 300. Likewise, the value -2.5e-4 is interpreted as -2.5/(104), or -0.00025.

Examples of Floating-Point Constants

Here are some examples of legal and illegal floating-point constants.
 
Table 15: Floating-Point Constants 
Constant  Legal or Illegal 
3. legal
35 legal - interpreted as an integer.
3.141 legal
3,500.45 illegal - commas are illegal.
.3333333333 legal
4E illegal - the exponent must be followed by a number
0.3 legal
-3e2 legal
4e3.6 illegal - the exponent must be an integer
3.0E5 legal
+3.6 legal
0.4E-5 legal

Character Constants

A character constant is any printable character or legal escape sequence enclosed in single quotes. A character constant can begin with the letter L to indicate that it is a wide character constant; this notation is ordinarily used for characters in an extended character set. In HP C, an ordinary character constant occupies one byte of storage; a wide character constant occupies the rightmost byte of a 4-byte integer.

The value of a character constant is the integer ISO Latin-1 value of the character. For example, the value of the constant x is 120.

Escape Sequences

HP C supports several escape sequences:
 
Table 16: Character Escape Codes 
Escape Code  Character  What it Does 
\a Audible alert Rings the terminal"s bell.
\b Backspace Moves the cursor back one space.
\f Formfeed Moves the cursor to the next logical page.
\n Newline Prints a newline.
\r Carriage return Prints a carriage return.
\t Horizontal tab Prints a horizontal tab.
\v Vertical tab Prints a vertical tab.
\\ Backslash Prints a backslash.
\? Question mark Prints a question mark.
\' Single quote Prints a single quote.
\" Double quote Prints a double quote.

The escape sequences for octal and hexadecimal numbers are commonly used to represent characters. For example, if ISO Latin-1 representations are being used, the letter a may be written as \141 or \x61 and Z as \132 or \x5A. This syntax is most frequently used to represent the null character as \0. This is exactly equivalent to the numeric constant zero (0). When you use the octal format, you do not need to include the zero prefix as you would for a normal octal constant.

Multi-Character Constants

Each character in an ordinary character constant takes up one byte of storage; therefore, you can store up to a 4-byte character constant in a 32-bit integer and up to a 2-byte character constant in a 16-bit integer.

For example, the following assignments are legal:

{
   char    x;              /* 1-byte integer */
   unsigned short int si;  /* 2-byte integer */
   unsigned long int li;   /* 4-byte integer */
 
/* the following two assignments are portable: */
   x  =  "j";      /* 1-byte character constant */
   li = L"j";      /* 4-byte wide char constant */
 
/* the following two assignments are not portable,
   and are not recommended: */
   si = "ef";     /* 2-character constant */
   li = "abcd";   /* 4-character constant */
}
The variable si is assigned the value of e and f, where each character takes up 8 bits of the 16-bit value. The HP C compiler places the last character in the rightmost (least significant) byte. Therefore, the constant ef will have a hexadecimal value of 6566. Since the order in which bytes are assigned is machine dependent, other machines may reverse the order, assigning f to the most significant byte. In that case, the resulting value would be 6665. For maximum portability, do not use multi-character constants. Use character arrays instead.

String Constants

A string constant is any series of printable characters or escape characters enclosed in double quotes. The compiler automatically appends a null character (\0) to the end of the string so that the size of the array is one greater than the number of characters in the string. For example,
"A short string"
becomes an array with 15 elements.

Like a character constant, a string constant can begin with the letter L to indicate that it is a string constant in an extended character set.

To span a string constant over more than one line, use the backslash character (\), also called the continuation character. The following, for instance, is legal:

    strcpy(string,"This is a very long string that requires more \
than one line");
Note that if you indent the second line, the spaces will be part of the string.

The compiler concatenates adjacent string constants. Therefore, you can also span a string constant over one line as shown:

strcpy(string, "This is a very long string that requires more "
               "than one line");
When you indent the second line with this method, the spaces are not part of the string.

The type of a string is array of char, and strings obey the same conversion rules as other arrays. Except when a string appears as the operand of sizeof or as an initializer, it is converted to a pointer to the first element of the string. Note also that the null string,

""
is legal, and contains a single trailing null character.

Structuring a C Program

When you write a C program, you can put all of your source code into one file or spread it across many files. A typical C source file contains some or all of the following components:

Example

The following shows how a program can be organized:

/* preprocessor directives */


#include <stdio.h>
#define WEIGHTING_FACTOR 0.6
/* global typedef declaration */
typedef float THIRTY_TWO_BIT_REAL;
/* global variable declaration */
THIRTY_TWO_BIT_REAL correction_factor = 1.15;
/* prototype */
float average (float arg1, THIRTY_TWO_BIT_REAL arg2)
/* start of function body */
{
   /* local variable declaration */
    float mean;
   /* assignment statement */
    mean = (arg1 * WEIGHTING_FACTOR) +
           (arg2 * (1.0 - WEIGHTING_FACTOR));
/* return statement */
     return (mean * correction_factor);
/* end of function body */
}
int main(void)
/* start of function body */
{
/* local variable declarations */ 
    float value1, value2, result;
/* statements */
    printf("Enter two values -- ");
    scanf("%f%f", &value1, &value2);
    result = average(value1, value2);
/* continuation line */
    printf("The weighted average using a correction \
factor of %4.2f is %5.2f\n", correction_factor, result);
/* end of function body */
}