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.
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.
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
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.
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.
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.
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.
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.
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.
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.
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.