Types


Type Classification · Arithmetic Types · Basic Integer Types · Bitfields · Enumerations · Floating-Point Types · Deriving Types · Pointer Types · Structure Types · Union Types · Array Types · Function Types · Incomplete Types · Type Qualifiers · Compatible and Composite Types


Type is a fundamental concept in Standard C. When you declare a name, you give it a type. Each expression and subexpression that you write has a type. This chapter shows each type you can write and how to classify it as either a function type, an object type, or an incomplete type. You see how an implementation can represent arithmetic types and how to derive more complex pointer types as well as others that are not scalar types. You learn how to use type qualifiers to specify access limitations for objects. This document also describes rules for forming a composite type from two compatible types.

Type Classification

Types have a number of classifications:

The diagram shows you, for example, that the type short is an integer type, an arithmetic type, a scalar type, and an object type. Similarly, a pointer to function is a pointer type, a scalar type, and an object type.

A type can be in any of three major classes:

Object types have a number of subclassifications. This document uses these subclassifications to simplify a number of descriptions. For example, you can declare a member of a structure to have any object type, you can compare against zero any value that has scalar type, you can multiply any two values that have arithmetic types, and you can form the inclusive OR of any two values that have integer types.

Arithmetic Types

The arithmetic types describe objects that represent numeric values. You use integer types to represent whole numbers, including zero or negative values. The three subclassifications of integer types are:

You use floating-point types to represent signed numbers that can have a fractional part. The range of values that you can represent with floating-point types is always much larger than the range you can represent with integer types, but the precision of floating-point values is limited. The translator predefines three floating-point types:

Basic Integer Types

The translator predefines nine basic integer types. You can designate some of these types in more than one way. For a designation that has more than one type specifier, you can write the type specifiers in any order. For example, you can write the designation unsigned short int as any of:

    unsigned short int    unsigned int short
    short unsigned int    short int unsigned
    int unsigned short    int short unsigned

The following table lists the properties of all basic integer types:

Alternate           Minimum        Restrictions on
Designations        Range          Representation

char                [0, 128)       same as either signed char
                                   or unsigned char

signed char         (-128, 128)    at least an 8-bit signed integer

unsigned char       [0, 256)       same size as signed char;
                                   no negative values

short               (-2^15, 2^15)  at least a 16-bit signed integer;
signed short                       at least as large as char
short int    
signed short int

unsigned short      [0, 2^16)      same size as short;
unsigned short int                 no negative values

int                 (-2^15, 2^15)  at least a 16-bit signed integer;
signed                             at least as large as short
signed int    
none

unsigned int        [0, 2^16)      same size as int;
unsigned                           no negative values

long                (-2^31, 2^31)  at least a 32-bit signed integer;
signed long                        at least as large as int
long int    
signed long int

unsigned long       [0, 2^32)      same size as long;
unsigned long int                  no negative values

If you write no type specifiers in a declaration, the type you specify is int. For example, the following declarations both declare x to have type int:

extern int x;
extern x;

This document refers to each predefined type by its first designation listed in the table, but written in italics. For example, unsigned short refers to the type you designate as unsigned short or as unsigned short int.

In this table, and in the tables that follow in this document, each minimum range is written as one or more ranges of values. The leftmost value is the lowest value in the range, and the rightmost is the highest. A left or right bracket indicates that the endpoint is included in the range. A left or right parenthesis indicates that the endpoint is not included in the range. Thus, the notation [0, 256) describes a range that includes 0 through 255.

The following table lists the powers of 2 used in the other tables in this document. An implementation can represent a broader range of values than shown here, but not a narrower range:

Power of 2  Decimal Value>

2^15        32,768
2^16        65,536
2^31        2,147,483,648
2^32        4,294,967,296

Bitfields

A bitfield is an integer that occupies a specific number of contiguous bits within an object that has an integer type. You can declare bitfields based on any of three different sets of integer type designations to designate three different kinds of bitfields:

You declare bitfields only as members of a structure or a union. The expression you write after the colon specifies the size of the bitfield in bits. You cannot portably specify more bits than are used to represent type int.

How the translator packs successive bitfield declarations into integer type objects is implementation defined.

The following table lists the properties of various kinds of bitfields:

Alternate     Minimum              Restrictions on
Designations  Range                Representation

int           [0, 2^(N-1))         same as either signed bitfields
none                               or unsigned bitfields

signed        (-2^(N-1), 2^(N-1))  N-bit signed integer;
signed int                         size not larger than int

unsigned      [0, 2^N)             N-bit unsigned integer;
unsigned int                       size not larger than int

For example, you can declare the flags register of some Intel 80X86 processors as:

struct flags {
    unsigned int
        cf:1, :1, pf:1, :1,
        af:1, :1, zf:1, sf:1,
        tf:1, if:1, df:1, of:1,
        iopl:2, nt:1, :1;
    };

assuming that the translator packs bitfields from least significant bit to most significant bit within a 16-bit object.

Enumerations

You declare an enumeration with one or more enumeration constants. For example:

enum Hue {black, red, green, blue = 4, magenta};

This declaration defines an enumeration type (with tag Hue) that has the enumeration constants black (with value 0), red (with value 1), green (with value 2), blue (with value 4), and magenta (with value 5).

An enumeration is compatible with the type that the translator chooses to represent it, but the choice is implementation defined. The translator can represent an enumeration as any integer type that promotes to int. A value you specify for an enumeration constant (such as 4 for blue above) must be an arithmetic constant expression and representable as type int. If you write:

enum Hue {red, green, blue = 4} x;
int *p = &x;         DANGEROUS PRACTICE

not all translators treat &x as type pointer to int.

Floating-Point Types

The translator predefines three floating-point types. All represent values that are signed approximations to real values, to some minimum specified precision, over some minimum specified range. The following table lists the properties of the three floating-point types:

             Minimum             Restrictions on
Designation  Range               Representation

float        [-10^+38, -10^-38]  at least 6 decimal digits
             0                   of precision
             [10^-38, 10^+38]

double       [-10^+38, -10^-38]  at least 10 decimal digits;
             0                   range and precision
             [10^-38, 10^+38]    at least that of float

long double  [-10^+38, -10^-38]  at least 10 decimal digits;
             0                   range and precision
             [10^-38, 10^+38]    at least that of double

No relationship exists between the representations of integer types and floating-point types.

Deriving Types

You can derive types from other types by declaring:

You cannot call a function that returns an incomplete type other than void. Any other incomplete return type must be complete before you call the function.

The following table summarizes the constraints on deriving types:

Derived      Function    Object         Incomplete
Type         Type        Type           Type

pointer to   any         any except      any
                         bitfield types

structure    ---         any             ---
containing

union        ---         any             ---
containing

array of     ---         any except      ---
                         bitfield types

function     ---         any except      any except
returning                bitfield types  incomplete
                         or array types  array types

Pointer Types

A pointer type describes an object whose values are the storage addresses that the program uses to designate functions or objects of a given type. You can declare a pointer type that points to any other type except a bitfield type. For example:

char *pc;                pc is a pointer to char
void *pv;                pv is a pointer to void
void (*pf)(void);        pf is a pointer to a function

Several constraints exist on the representation of pointer types:

No relationship exists between the representations of pointer types and integer or floating-point types.

Structure Types

A structure type describes an object whose values are composed of sequences of members that have other object types. For example:

struct {
    char ch;             struct contains a char
    long lo;             followed by a long
    } st;                st contains st.ch and st.lo

The members occupy successive locations in storage, so an object of structure type can represent the value of all its members at the same time. Every structure member list (enclosed in braces) within a given translation unit defines a different (incompatible) structure type.

Some implementations align objects of certain types on special storage boundaries. A Motorola 680X0, for example, requires that a long object be aligned on an even storage boundary. (The byte with the lowest address, used to designate the entire object, has an address that is a multiple of 2.)

A structure type can contain a hole after each member to ensure that the member following is suitably aligned. A hole can occur after the last member of the structure type to ensure that an array of that structure type has each element of the array suitably aligned. In the Motorola 68000 example above, a one-byte (or larger) hole occurs after the member ch, but a hole probably does not occur after the member lo. Holes do not participate in representing the value of a structure.

Union Types

A union type describes an object whose values are composed of alternations of members that have other object types. For example:

union {
    char ch;             union contains a char
    long lo;             followed by a long
    } un;                un contains un.ch or un.lo

All members of a union type overlap in storage, so an object of union type can represent the value of only one of its members at any given time. Every union member list (enclosed in braces) within a translation unit defines a different (incompatible) union type.

Like a structure type, a union type can have a hole after each of its members. The holes are at least big enough to ensure that a union type occupies the same number of bytes (regardless of which member is currently valid) and to ensure that an array of that union type has each element of the array suitably aligned.

Array Types

An array type describes an object whose values are composed of repetitions of elements that have some other object type. For example:

char ac[10];             contains chars ac[0], ac[1], etc.

Elements of an array type occupy successive storage locations, beginning with element number zero, so an object of array type can represent multiple element values at the same time.

The number of elements in an array type is specified by its repetition count. In the example above, the repetition count is 10. An array type does not contain additional holes because all other types pack tightly when composed into arrays. The expression you write for a repetition count must be an arithmetic constant expression whose value is greater than zero.

Function Types

A function type describes a function whose return value is either an object or an incomplete type other than an array type. A void return type indicates that the function returns no result. A function type can also describe the number and types of arguments needed in an expression that calls the function. For example:

double sinh(double x);   one double argument,
                         returns double result
void wrapup(void);       no argument or return value

A function type does not represent a value. Instead, it describes how an expression calls (passes control to) a body of executable code. When the function returns (passes control back) to the expression that calls it, it can provide a return value as the value of the function call subexpression.

Incomplete Types

An incomplete type can be a structure type whose members you have not yet specified, a union type whose members you have not yet specified, an array type whose repetition count you have not yet specified, or a void type. You complete an incomplete type by specifying the missing information. Once completed, an incomplete type becomes an object type.

You create an incomplete structure type when you declare a structure type without specifying its members. For example:

struct complex *pc;      pc points to incomplete
                         structure type complex

You complete an incomplete structure type by declaring the same structure type later in the same scope with its members specified, as in:

struct complex {
    float re, im;
    };                   complex now completed

You create an incomplete union type when you declare a union type without specifying its members. For example:

union stuff *ps;         ps points to incomplete
                         union type stuff

You complete an incomplete union type by declaring the same union type later in the same scope with its members specified, as in:

union stuff {
    int in;
    float fl;
    };                   stuff now completed

You create an incomplete array type when you declare an object that has array type without specifying its repetition count. For example:

char a[];                a has incomplete array type

You complete an incomplete array type by redeclaring the same name later in the same scope with the repetition count specified, as in:

char a[25];              a now has complete type

You write a void type as the keyword void, with an optional (but meaningless) type qualifier. You can declare but you cannot define an object of void type. You cannot complete a void type.

Type Qualifiers

You can qualify any object type or incomplete type with any combination of the two type qualifiers const and volatile. Each type qualifier designates a qualified version of some type. The qualified and unqualified versions of a type have the same representation:

You write a type qualifier within a declaration either as part of the type part or as part of a pointer decoration. All combinations of pointer decorations and type qualifiers are meaningful. A few examples are:

volatile int vi;         vi is a volatile int
const int *pci;          pci points to const int
int *const cpi;          cpi is a const pointer to int
const int *const cpci;   cpci is a const pointer to const int
const int *volatile      vpci is a volatile pointer
    vpci;                to const int

Moreover, all four combinations of type qualifiers are meaningful:

If you declare an object as having a const-qualified type (such as cpi in the example above), then no expression within the program should attempt to alter the value stored in the object. The implementation can place the object in read-only memory (ROM) or replace references to its stored value with the known value.

A pointer to const-qualified type can point to an object that does not have const-qualified type. A pointer to a type that is not const-qualified can point to an object that has const-qualified type. You should not, however, alter the value stored in the object with such a pointer.

For example:

const int ci, *pci;
int i, *pi;
pci = &i;                permissible
pi = (int *)&ci;         type cast required
i = *pci + *pi;          permissible
*pci = 3;                INVALID: *pci is const
*pi = 3;                 INVALID: ci is const

If you declare an object as having a volatile-qualified type (such as vi in the example above), then no expression within the program should access or alter the value stored in the object via an lvalue that does not have a volatile-qualified type.

A pointer to volatile-qualified type can point to an object that does not have volatile-qualified type. A pointer to a type that is not volatile-qualified can point to an object that has volatile-qualified type. You should not, however, access the object with such a pointer.

Compatible and Composite Types

In many contexts, the translator must test whether two types are compatible, which occurs when one of the following conditions is met:

Some examples of compatible types are:

long                     is compatible with long
long                     is compatible with signed long
char a[]                 is compatible with char a[10]
int f(int i)             is compatible with int f()

Two types are assignment compatible if they form a valid combination of types for the assignment operator (=).

The translator combines compatible types to form a composite type. The composite type is determined in one of the following ways:

For example, the following two types are compatible:

FILE *openit(char *)     and FILE *openit()

They have the composite type:

FILE *openit(char *)

For a more complex example, the two types:

void (*apf[])(int x)     and void (*apf[20])()

are compatible and have the composite type:

void (*apf[20])(int x)

See also the Table of Contents and the Index.

Copyright © 1989-1996 by P.J. Plauger and Jim Brodie. All rights reserved.