United States

Compaq C

# Compaq C
Language Reference Manual

Previous | Contents | Index

# 3.3 Floating-Point Types

The floating-point types are:

- `float` ---32 bits
- `double` ---64 bits
- `long double` (OPENVMS ALPHA)---128 bits by default, with the option for 64 bits
- `long double` (TRU64 UNIX)---64 bits in current versions of Tru64 UNIX
- `long double` (VAX)---64 bits
- `float _Complex` (ALPHA ONLY)
- `double _Complex` (ALPHA ONLY)
- `long double _Complex` (ALPHA ONLY)

Use the floating-point types for variables, constants, and function return values with fractional parts, or where the value exceeds the storage range available with the integral types. The following examples show sample floating-point type declarations (and initializations):

    float x = 35.69;
    double y = .0001;
    double z = 77.0e+10;
    float Q = 99.9e+99;                 /*  Exceeds allowable range   */

Complex Type (ALPHA ONLY)

The C99 standard introduces a built-in complex data type similar to the Fortran type, in all three precisions ( `float _Complex` , `double _Complex` , and `long double _Complex` ). It also has an associated header file, `<complex.h>` The `<complex.h>` header file defines a macro spelled "complex", intended to be the preferred way to refer to the types. (See Section 9.2).

A complex type has the same representation and alignment requirements as an array type containing exactly two elements of the corresponding real type; the first element is equal to the real part, and the second element to the imaginary part, of the complex number.

The type is similar to the Fortran type in its use. There is no special syntax for constants; instead there is a new keyword `_Complex_I` , which has a complex value whose real part is zero and whose imaginary part is 1.0. The header file defines a macro `I` that expands to `_Complex_I` , and so a complex constant with equal real and imaginary parts of 2.0 would be written as `2.0 + 2.0*I` .

There are some known issues with complex types on Compaq C:

- The complex data types are not available when using the /FLOAT=D_FLOAT command-line option. This is a permanent restriction.
- The complex types and functions other than `long double complex` are available on versions of OpenVMS prior to Version 7.3. The `long double complex` type is available with OpenVMS Version 7.3.
- Initialized declarations of `long double complex` variables cause a compiler assertion failure when generating a machine-code listing.
- Functions named `cabs` , `cabsf` , and `cabsl` have traditionally been declared in <code_example>(<math.h>) using a struct representation to hold two floating values. This is not compatible with the calling standard for passing complex values. To access working cabs functions, you must include `<complex.h>` before you include <code_example>(<math.h>).

Note The C99 standard reserves the keyword `_Imaginary` for use as a type-specifier in conjunction with an experimental/optional feature called a "pure imaginary" type, specified in informative Annex G. In Compaq C, use of the `_Imaginary` keyword produces a warning, which is resolved by treating it as an ordinary identifier.

# 3.4 Derived Types

There are five derived types in C:

- Function types
- Pointer types
- Array types
- Structure types
- Union types

The following sections describe these derived types.

A derived type is formed by using one or more basic types in combination. Using derived types, an infinite variety of new types can be formed. The array and structure types are collectively called the aggregate types. Note that the aggregate types do not include union types, but a union may contain an aggregate member.

## 3.4.1 Function Type

A function type describes a function that returns a value of a specified type. If the function returns no value, it should be declared as "function returning `void` " as follows:

    void function1 ();

In the following example, the data type for the function is "function returning `int` ":

    int uppercase(int lc)
    {
      int uc = lc + 0X20;
      return uc;
    }

Chapter 4 discusses declarations in general. Chapter 5 covers functions specifically, including their declarations, parameters, and argument passing.

## 3.4.2 Pointer Type

A pointer type describes a value that represents the address of an object of a stated type. A pointer is stored as an integral value that references the address of the target object. Pointer types are derived from other types, called the referenced type of the pointer. For example:

    int *p;          /*  p is a pointer to an int type                 */
    double *q();     /*  q is a function returning a pointer to an
                         object of type double                         */
    int (*r)[5];     /*  r is a pointer to an array of five elements   */
                     /*  (r holds the address to the first element of
                         the array)                                    */
    const char s[6]; /*  s is a const-qualified array of 6 elements    */

The pointer itself can have any storage class, but the object addressed by the pointer cannot have the `register` storage class or be a bit field. Pointers to qualified or unqualified versions of compatible types have the same representation and alignment requirements as the target type. Pointers to other types need not have the same representation or alignment requirements.

The construction `void *` designates a generic "pointer to `void` " type. The `void *` construction can be used to point to an object of any type, and it is most useful when a pointer is needed to point to the address of objects with different or unknown types (such as in a function prototype). A pointer to `void` can also be converted to or from a pointer of any other type, and has the same representation and alignment requirements as a pointer to a character type.

A pointer to the address 0 (zero) is called a null pointer. Null pointers are often used to indicate that no more members of a list exist (for example, when using pointers to show the next member of the list). Dereferencing a null pointer with the * or subscripting operators leads to unpredictable and usually very unfavorable results.

See Chapter 4 for details on the syntax of pointer declarations.

## 3.4.3 Array Type

An array type can be formed from any valid completed type. Completion of an array type requires that the number and type of array members be explicitly or implicitly specified. The member types can be completed in the same or a different compilation unit. Arrays cannot be of `void` or function type, since the `void` type cannot be completed and function types are not object types requiring storage.

Typically, arrays are used to perform operations on some homogeneous set of values. The size of the array type is determined by the data type of the array and the number of elements in the array. Each element in an array has the same type. For example, the following definition creates an array of four characters:

    char x[] = "Hi!"   /*  Declaring an array x   */;

Each of the elements has the size of a `char` object, 8 bits. The size of the array is determined by its initialization; in the previous example, the array has three explicit elements plus one null character. Four elements of 8 bits each results in an array with a size of 32 bits.

An array is allocated contiguously in memory, and cannot be empty (that is, have no members). An array can have only one dimension. To create an array of "two dimensions," declare an array of arrays, and so on.

It is possible to declare an array of unknown size; this sort of declaration is called an incomplete array declaration, because the size is not specified. The following example shows an incomplete declaration:

    int x[];

The size of an array declared in this manner must be specified elsewhere in the program. (See Section 4.7 for more information on declaring incomplete arrays and initializing arrays.)

Character strings (string literals) are stored in the form of an array of `char` or `wchar_t` type, and are terminated by the null character (\0).

An array in C has only one dimension. An array of arrays can be declared, however, to create a multidimensional array. The elements of these arrays are stored in increasing addresses so that the rightmost subscript varies most rapidly. This is called row-major order, and is analogous to a car's odometer. For example, in an array of two arrays declared as `int a[2][3];` the elements are stored in this order:

    a[0][0], a[0][1], a[0][2], a[1][0], a[1][1], a[1][2]

## 3.4.4 Structure Type

A structure type is a sequentially allocated nonempty set of objects, called members. Structures let you group heterogeneous data. They are much like records in Pascal. Unlike arrays, the elements of a structure need not be of the same data type. Also, elements of a structure are accessed by name, not by subscript. The following example declares a structure `employee` , with two structure variables ( `ed` and `mary` ) of the structure type `employee` :

    struct employee { char name[30]; int age; int empnumber; };
    struct employee ed, mary;

Structure members can have any type except an incomplete type, such as the `void` type or a function type. Structures can contain pointers to objects of their own type, but they cannot contain an object of their own type as a member; such an object would have an incomplete type. For example:

    struct employee {
      char name[30];
      struct employee div1;       /*  This is invalid. */
      int *f();
    };

The following example, however, is valid:

    struct employee {
      char name[30];
      struct employee *div1;/*  Member can contain pointer to employee
                                structure.                             */
      int (*f)();           /*  Pointer to a function returning int    */
    };

The name of a declared structure member must be unique within the structure, but it can be used in another nested or unnested structure or name spaces to refer to a different object. For example:

    struct {
      int a;
      struct {
        int a;  /* This 'a' refers to a different object
                   than the previous 'a'               */
      };
    };

Chapter 4 contains more examples on structures and their declarations.

The compiler assigns storage for structure members in the order of member declaration, with increasing memory addresses for subsequent members. The first member always begins at the starting address of the structure itself. Subsequent members are aligned per the alignment unit, which may differ depending on the member sizes in the structure. A structure may contain padding (unused bits) so that members of an array of such structures are properly aligned, and the size of the structure is the amount of storage necessary for all members plus any padded space needed to meet alignment requirements. See your system's Compaq C documentation for platform-specific information about structure alignment and representation.

A pragma is available to change the alignment of a structure on one platform to match that of structures on other platforms. See Section B.29 for more information on this pragma.

## 3.4.5 Union Type

A union type can store objects of different types at the same location in memory. The different union members can occupy the same location at different times in the program. The declaration of a union includes all members of the union, and lists the possible object types the union can hold. The union can hold any one member at a time---subsequent assignments of other members to the union overwrite the existing object in the same storage area.

Unions can be named with any valid identifier. An empty union cannot be declared, nor can a union contain an instance of itself. A member of a union cannot have a `void` , function, or incomplete type. Unions can contain pointers to unions of their type.

Another way to look at a union is as a single object that can represent objects of different types at different times. Unions let you use objects whose type and size can change as the program progresses, without using machine-dependent constructions. Some other languages call this concept a variant record.

The syntax for defining unions is very similar to that for structures. Each union type definition creates a unique type. Names of union members must be unique within the union, but they can be duplicated in other nested or unnested unions or name spaces. For example:

    union {
      int a;
      union {
        int a;  /* This 'a' refers to a different object
                   than the previous 'a'                */
      };
    };

The size of a union is the amount of storage necessary for its largest member, plus any padding needed to meet alignment requirements.

Once a union is defined, a value can be assigned to any of the objects declared in the union declaration. For example:

    union name {
      double dvalue;
      struct x { int value1; int value2; };
      float fvalue;
    } alberta;
    alberta.dvalue = 3.141596; /* Assigns the value of pi to the union object */

Here, `alberta` can hold a `double` , `struct` , or `float` value. The programmer has responsibility for tracking the current type of object contained in the union. An assignment expression can be used to change the type of value held in the union.

Undefined behavior results when a union is used to store a value of one type, and then the value is accessed through another type. For example:

    /*
        Assume that `node' is a typedef_name for objects for which
        information has been entered into a hash table;

        `hash_entry' is a structure describing an entry in the hash table.
        The member `hash_value' is a pointer to the relevant `node'.
     */
    typedef struct hash_entry
    {
       struct hash_entry *next_hash_entry;
       node   *hash_value;
       /* ... other information may be present ... */
    } hash_entry;

    extern hash_entry *hash_table [512];

    /*
        `hash_pointer' is a union whose members are a pointer to a
        `node' and a structure containing three bit fields that
        overlay the pointer value.  Only the second bit field is
        being used, to extract a value from the middle
        of the pointer to be used as an index into the hash table.
        Note that nine bits gives a range of values from 0 to 511;
        hence, the size of `hash_table' above.
     */
    typedef union
    {
       node *node_pointer;
       struct
       {
        unsigned : 4;
        unsigned  index : 9;
        unsigned :19;
       } bits;
    } hash_pointer;

# 3.5 void Type

The `void` type is an incomplete type that cannot be completed.

The `void` type has three important uses:

- To signify that a function returns no value
- To indicate a generic pointer (one that can point to any type object)
- To specify a function prototype with no arguments

The following example shows how `void` is used to define a function, with no parameters, that does not return a value:

    void message(void)
    {
      printf ("Stop making sense!");
    }

The next example shows a function prototype for a function that accepts a pointer to any object as its first and second argument:

    void memcopy (void *dest, void *source, int length);

A pointer to the `void` type has the same representation and alignment requirements as a pointer to a character type. The `void *` type is a derived type based on `void` .

The `void` type can also be used in a cast expression to explicitly discard or ignore a value. For example:

    int tree(void);

    void main()
    {
      int i;

      for (; ; (void)tree()){...}  /* void cast is valid                  */

      for (; (void)tree(); ;){...} /* void cast is NOT valid, because the */
                                   /* value of the second expression in a */
                                   /* for statement is used               */

      for ((void)tree(); ;) {...}  /* void cast is valid                  */

    }

A `void` expression has no value, and cannot be used in any context where a value is required.

# 3.6 Enumerated Types

An enumerated type is used to specify the possible values of an object from a predefined list. Elements of the list are called enumeration constants. The main use of enumerated types is to explicitly show the symbolic names, and therefore the intended purpose, of objects whose values can be represented with integer values.

Objects of enumerated type are interpreted as objects of type `signed int` , and are compatible with objects of other integral types.

The compiler automatically assigns integer values to each of the enumeration constants, beginning with 0. The following example declares an enumerated object `background_color` with a list of enumeration constants:

    enum colors { black, red, blue, green, white } background_color;

Later in the program, a value can be assigned to the object `background_color` :

    background_color = white;

In this example, the compiler automatically assigns the integer values as follows: `black` = 0, `red` = 1, `blue` = 2, `green` = 3, and `white` = 4. Alternatively, explicit values can be assigned during the enumerated type definition:

    enum colors { black = 5, red = 10, blue, green = 7, white = green+2 };

Here, `black` equals the integer value 5, `red` = 10, `blue` = 11, `green` = 7, and `white` = 9. Note that `blue` equals the value of the previous constant ( `red` ) plus one, and `green` is allowed to be out of sequential order.

Because the ANSI C standard is not strict about assignment to enumerated types, any assigned value not in the predefined list is accepted without complaint.

# 3.7 Type Qualifiers

There are four type qualifiers:

- `const`
- `volatile`
- `__unaligned` (axp)
- `__restrict` (pointer type only)

Type qualifiers were introduced by the ANSI C standard to, in part, give you greater control over the compiler's optimizations. The `const` and `volatile` type qualifiers can be applied to any type. The `__restrict` type qualifier can be applied only to pointer types.

Note that because the `__restrict` type qualifier is not part of the 1989 ANSI C standard, this keyword has double leading underscores. The next version (9X) of the C standard is expected to adopt the keyword `restrict` with the same semantics described in this section.

The use of `const` gives you a method of controlling write access to an object, and eliminates potential side effects across function calls involving that object. This is because a side effect is an alteration of an object's storage and `const` prohibits such alteration.

Use `volatile` to qualify an object that can be changed by other processes or hardware. The use of `volatile` disables optimizations with respect to referencing the object. If an object is volatile qualified, it may be changed between the time it is initialized and any subsequent assignments. Therefore, it cannot be optimized.

Function parameters, however, do not all share the type qualification of one parameter. For example:

    int f( const int a, int b)   /*  a is const qualified; b is not  */

When using a type qualifier with an array identifier, the elements of the array are qualified, not the array type itself.

The following declarations and expressions show the behavior when type qualifiers modify an array or structure type:

    const struct s { int mem; } cs = { 1 };
    struct s ncs;                        /* ncs is modifiable         */
    typedef int A[2][3];
    const A a = {{4, 5, 6}, {7, 8, 9}};  /*  array of array of const  */
                                         /*  int's                    */
    int *pi;
    const int *pci;

    ncs = cs;            /*  Valid                                    */
    cs = ncs;            /*  Invalid, cs is const-qualified           */
    pi = &ncs.mem;       /*  Valid                                    */
    pi = &cs.mem;        /*  Violates type constraints for = operator */
    pci = &cs.mem;       /*  Valid                                    */
    pi = a[0];           /*  Invalid; a[0] has type "const int *"     */

Previous | Next | Contents | Index

privacy and legal statement