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.
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.
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:
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
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.
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//CODE> (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:
- pointers to other types
- structures
containing other object types
- unions
containing other object types
- arrays of other object types
- functions
that return object or incomplete types
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:
- Every pointer type can represent a
null pointer value
that compares equal to an integer zero, and does not equal the address
of any function or object in the program.
- The types pointer to char, pointer to signed char, pointer
to unsigned char, and pointer to void
share the same representation.
- Any valid object pointer can safely be converted to
pointer to void and back to the original type.
- All pointer to function types share the same representation
(which need not be the same as for pointer to void).
- Otherwise, different pointer types can have
different representations.
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:
- A
const-qualified type
indicates that access to the designated
object cannot alter the value stored in the object. All other object
types can have their values altered.
- A
volatile-qualified type
indicates that agencies unknown
to the translator can access or alter the value stored in the object.
The translator can assume that it has complete control of all objects
that do not have volatile-qualified types.
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:
- You specify no type qualifiers for the ``normal'' objects
in the program.
- You specify const-qualified types for objects that the
program does not alter (such as tables of constant values).
- You specify volatile-qualified types for objects accessed
or altered by signal handlers, by concurrently executing programs,
or by special hardware (such as a memory-mapped I/O control register).
- You specify both const- and volatile-qualified
types for objects that the program does not alter, but that other
agencies can alter (such as a memory-mapped interval timer).
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:
- Both types are the same.
- Both are
pointer types,
with the same type qualifiers, that
point to compatible types.
- Both are
array types
whose elements have compatible types. If
both specify repetition counts, the repetition counts are equal.
- Both are
function types
whose return types are compatible. If
both specify types for their parameters, both
declare the same number
of parameters (including ellipses) and the types of corresponding
parameters are compatible.
Otherwise, at least one does not specify types for
its parameters. If the other specifies types for its parameters, it
specifies only a fixed number of parameters
and does not specify parameters of type
float or of any
integer types that change when
promoted.
- Both are
structure,
union, or
enumeration types that are declared
in different translation units with the same member names. Structure
members are declared in the same order. Structure and union members
whose names match are declared with compatible types. Enumeration
constants whose names match have the same values.
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 two types that are the same, it is the common type.
- For two
pointer types,
it is a similarly qualified pointer to
the composite type pointed to.
- For two
array types,
it is an array of elements with the composite
of the two element types. If one of the array types specifies a repetition
count, that type provides the repetition count for the composite type.
Otherwise, the composite has no repetition count.
- For two
function types,
it is a function type that returns a
composite of the two return types. If both specify types for their
parameters, each parameter type in the composite type is the composite
of the two corresponding parameter types. If only one specifies types
for its parameters, it determines the parameter types in the composite
type. Otherwise, the composite type
specifies no types for its parameters.
- For two
structure,
union, or
enumeration types, it is the type
declared in the current translation unit.
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.