Programs have to store and process different kinds of data, such as integers and floating-point numbers, in different ways. To this end, the compiler needs to know what kind of data a given value represents.
In C, the term object refers to a location in memory whose contents can represent values. Objects that have names are also called variables. An object’s type determines how much space the object occupies in memory, and how its possible values are encoded. For example, the same pattern of bits can represent completely different integers depending on whether the data object is interpreted as signed (that is, either positive or negative) or unsigned (and hence unable to represent negative values).
The types in C can be classified as follows:
Basic types
Standard and extended integer types
Real and complex floating-point types
Enumerated types
The type void
Derived types
Pointer types
Array types
Structure types
Union types
Function types
The basic types and the enumerated types together make up the arithmetic types. The arithmetic types and the pointer types together are called the scalar types. Finally, array types and structure types are referred to collectively as the aggregate types. (Union types are not considered aggregate because only one of their members can store a value at any given time.)
A function type describes the interface to a function; that is, it specifies the type of the function’s return value, and may also specify the types of all the parameters that are passed to the function when it is called.
All other types describe objects. This description may or may not include the object’s storage size. If it does, the type is properly called an object type; if not, it is an incomplete type. An example of an incomplete type might be an externally defined array variable:
externfloatfArr[];// External declaration
This line declares fArr as an array whose elements have type float. However, because the array’s size is not specified here, fArr’s type is incomplete. As long as the global array fArr is defined with a specified size at another location in the program—in another source file, for example—this declaration is sufficient to let you use the array in its present scope. (For more details on external declarations, see Chapter 11.)
This chapter describes the basic types, enumerations, and the type void. The derived types are described in Chapters 7 through 10.
Some types are designated by a sequence of more than one keyword, such as unsigned short. In such cases, the keywords can be written in any order. However, there is a conventional keyword order, which we use in this book.
There are five signed integer types. Most of these types can be designated by several synonyms, which are listed in Table 2-1.
| Type | Synonyms |
|---|---|
|
|
|
|
|
|
|
|
|
|
For each of the five signed integer types in Table 2-1, there is also a corresponding unsigned type that occupies the same amount of memory, with the same alignment. In other words, if the compiler aligns signed int objects on even-numbered byte addresses, then unsigned int objects are also aligned on even addresses. These unsigned types are listed in Table 2-2.
| Type | Synonyms |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
C99 introduced the unsigned integer type _Bool to represent Boolean truth values. The Boolean value true is coded as 1, and false is coded as 0. If you include the header file stdbool.h in a program, you can also use the identifiers bool, true, and false, which are familiar to C++ programmers. The macro bool is a synonym for the type _Bool, and true and false are symbolic constants equal to 1 and 0.
The type char is also one of the standard integer types. However, the one-word type name char is synonymous either with signed char or with unsigned char, depending on the compiler. Because this choice is left up to the implementation, char, signed char, and unsigned char are formally three different types.
If your program relies on char being able to hold values less than zero or greater than 127, you should be using either signed char or unsigned char instead.
You can do arithmetic with character variables. It’s up to you to decide whether your program interprets the number in a char variable as a character code or as something else. For example, the following short program treats the char value in ch as both an integer and a character, but at different times:
charch='A';// A variable with type charprintf("The character %c has the character code %d.\n",ch,ch);for(;ch<='Z';++ch)printf("%2c",ch);
In the printf() statement, ch is first treated as a character that gets displayed, and then as numeric code value of the character. Likewise, the for loop treats ch as an integer in the instruction ++ch, and as a character in the printf() function call. On systems that use the 7-bit ASCII code or an extension of it, the code produces the following output:
The character A has the character code 65. A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
A value of type char always occupies one byte—in other words, sizeof(char) always yields 1—and a byte is at least eight bits wide. Every character in the basic character set can be represented in a char object as a positive value.
C defines only the minimum storage sizes of the other standard types: the size of type short is at least two bytes, long is at least four bytes, and long long is at least eight bytes. Furthermore, although the integer types may be larger than their minimum sizes, the sizes implemented must be in the order:
sizeof(short) ≤ sizeof(int) ≤ sizeof(long) ≤ sizeof(long long)
The type int is the integer type best adapted to the target system’s architecture, with the size and bit format of a CPU register.
The internal representation of integer types is binary. Signed types may be represented in binary as sign and magnitude, as a one’s complement, or as a two’s complement. The most common representation is the two’s complement. The non-negative values of a signed type are within the value range of the corresponding unsigned type, and the binary representation of a non-negative value is the same in both the signed and unsigned types. Table 2-3 shows the different interpretations of bit patterns as signed and unsigned integer types.
| Binary | Decimal value as unsigned int | Decimal value as signed int, one’s complement | Decimal value as signed int, two’s complement |
|---|---|---|---|
00000000 00000000 |
0 |
0 |
0 |
00000000 00000001 |
1 |
1 |
1 |
00000000 00000010 |
2 |
2 |
2 |
… |
|||
01111111 11111111 |
32,767 |
32,767 |
32,767 |
10000000 00000000 |
32,768 |
-32,767 |
-32,768 |
10000000 00000001 |
32,769 |
-32,766 |
-32,767 |
… |
|||
11111111 11111110 |
65,534 |
-1 |
-2 |
11111111 11111111 |
65,535 |
-0 |
-1 |
Table 2-4 lists the sizes and value ranges of the standard integer types.
| Type | Storage size | Minimum value | Maximum value | ||
|---|---|---|---|---|---|
char |
(Same as either signed char or unsigned char) | ||||
unsigned char |
One byte | 0 | 255 | ||
signed char |
One byte | -128 | 127 | ||
int |
Two bytes or four bytes | -32,768 or -2,147,483,648 | 32,767 or 2,147,483,647 | ||
unsigned int |
Two bytes or four bytes | 0 | 65,535 or 4,294,967,295 | ||
short |
Two bytes | -32,768 | 32,767 | ||
unsigned short |
Two bytes | 0 | 65,535 | ||
long |
Four bytes | -2,147,483,648 | 2,147,483,647 | ||
unsigned long |
Four bytes | 0 | 4,294,967,295 | ||
long long (C99) |
Eight bytes | -9,223,372,036, 854,775,808 | 9,223,372,036, 854,775,807 | ||
unsigned long long (C99) |
Eight bytes | 0 | 18,446,744,073, 709,551,615 | ||
In the following example, each of the int variables iIndex and iLimit occupies four bytes on a 32-bit computer:
intiIndex,// Define two int variables andiLimit=1000;// initialize the second one.
To obtain the exact size of a type or variable, use the sizeof operator. The expression
sizeof(type)
yields the size of the type named, and
sizeofexpression
yields the size of the given expression’s type, as a number of bytes with the type size_t. The type size_t is defined in stddef.h, stdio.h, and other header files as an unsigned integer type (such as unsigned long, for example). If the operand is an expression, the size is that of the expression’s type. In the previous example, the value of sizeof(int) would be the same as sizeof(iIndex)—namely, 4. The parentheses around the expression iIndex can be omitted because iIndex is an expression, not a type.
You can find the value ranges of the integer types for your C compiler in the header file limits.h, which defines macros such as INT_MIN, INT_MAX, UINT_MAX, and so on (see Chapter 16). The program in Example 2-1 uses these macros to display the minimum and maximum values for the types char and int.
// limits.c: Display the value ranges of char and int.// ---------------------------------------------------#include <stdio.h>#include <limits.h>// Contains the macros CHAR_MIN, INT_MIN, etc.intmain(){printf("Storage sizes and value ranges of the types char and int\n\n");printf("The type char is %s.\n\n",CHAR_MIN<0?"signed":"unsigned");printf(" Type Size (in bytes) Minimum Maximum\n""---------------------------------------------------\n");printf(" char %8zu %20d %15d\n",sizeof(char),CHAR_MIN,CHAR_MAX);printf(" int %8zu %20d %15d\n",sizeof(int),INT_MIN,INT_MAX);return0;}
In arithmetic operations with integers, overflows can occur. An overflow happens when the result of an operation is no longer within the range of values that the type being used can represent. In arithmetic with unsigned integer types, overflows are ignored. In mathematical terms, that means that the effective result of an unsigned integer operation is equal to the remainder of a division by UTYPE_MAX + 1, where UTYPE_MAX is the unsigned type’s maximum representable value. For example, the following addition causes the variable to overflow:
unsignedintui=UINT_MAX;ui+=2;// Result: 1
C specifies this behavior only for the unsigned integer types. For all other types, the result of an overflow is undefined. For example, the overflow may be ignored, or it may raise a signal that aborts the program if it is not caught.
The headers of the standard library define numerous integer types for specific uses, such as the type wchar_t to represent wide characters. These types are typedef names—that is, synonyms for standard integer types (see “typedef Declarations”).
The types ptrdiff_t, size_t, and wchar_t are defined in the header stddef.h (and in other headers); the types char16_t and char32_t are defined in the header uchar.h. For special requirements, integer types with specifed bit widths, in signed and unsigned variants, are defined in the header stdint.h. These are described in the following subsection.
Furthermore, the header stdint.h also defines macros that supply the maximum and minimum representable values of all the integer types defined in the standard library. For example, SIZE_MAX equals the largest value you can store in a variable of the type size_t. For all details on the types listed here, and the corresponding macros, see Chapter 16.
The width of an integer type is defined as the number of bits used to represent a value, including the sign bit. Typical widths are 8, 16, 32, and 64 bits. For example, the type int is at least 16 bits wide.
In C99, the header file stdint.h defines integer types to fulfill the need for known widths. These types are listed in Table 2-5. Those types whose names begin with u are unsigned. C99 implementations are not required to provide the types marked as “optional” in the table.
| Type | Meaning | Implementation |
|---|---|---|
|
An integer type whose width is exactly |
Optional |
|
An integer type whose width is at least |
Required for |
|
The fastest type to process whose width is at least |
Required for |
|
The widest integer type implemented |
Required |
|
An integer type wide enough to store the value of a pointer |
Optional |
For example, int_least64_t and uint_least64_t are integer types with a width of at least 64 bits. If an optional signed type (without the prefix u) is defined, then the corresponding unsigned type (with the initial u) is required, and vice versa. The following example defines and initializes an array whose elements have the type int_fast32_t:
#define ARR_SIZE 100int_fast32_tarr[ARR_SIZE];// Define an array arr// with elements of type int_fast32_tfor(inti=0;i<ARR_SIZE;++i)arr[i]=(int_fast32_t)i;// Initialize each element
The types listed in Table 2-5 are usually defined as synonyms for existing standard types. For example, the stdint.h file supplied with one C compiler contains the line:
typedefsignedcharint_fast8_t;
This declaration simply defines the new type int_fast8_t (the fastest 8-bit signed integer type) as being equivalent with signed char.
Furthermore, an implementation may also define extended integer types such as int24_t or uint_least128_t.
The signed intN_t types have a special feature: they must use the two’s complement binary representation. As a result, their minimum value is −2N−1, and their maximum value is 2N−1 − 1.
The value ranges of the types defined in stdint.h are also easy to obtain: macros for the greatest and least representable values are defined in the same header file. The names of the macros are the uppercased type names, with the suffix _t (for type) replaced by _MAX or _MIN (see Chapter 16). For example, the following definition initializes the variable i64 with its smallest possible value:
int_least64_ti64=INT_LEAST64_MIN;
The
header file inttypes.h includes the header file stdint.h, and provides other features such as extended integer type specifiers for use in printf() and scanf() function calls (see Chapter 16).
C also includes special numeric types that can represent nonintegers with a decimal point in any position. The standard floating-point types for calculations with real numbers are as follows:
floatFor variables with single precision
doubleFor variables with double precision
long doubleFor variables with extended precision
A floating-point value can be stored only with a limited precision, which is determined by the binary format used to represent it and the amount of memory used to store it. The precision is expressed as a number of significant digits. For example, a “precision of six decimal digits” or “six-digit precision” means that the type’s binary representation is precise enough to store a real number of six decimal digits, so that its conversion back into a six-digit decimal number yields the original six digits. The position of the decimal point does not matter, and leading and trailing zeros are not counted in the six digits. The numbers 123,456,000 and 0.00123456 can both be stored in a type with six-digit precision.
In C, arithmetic operations with floating-point numbers are usually performed with double or greater precision. The floating-point precision used internally by the given implementation is indicated by the value of the macro FLT_EVAL_METHOD, defined in the header float.h. For example, if the macro FLT_EVAL_METHOD has the value 1, the following product is calculated using the double type:
floatheight=1.2345,width=2.3456;// Float variables have// single precision.doublearea=height*width;// The actual calculation// is performed with// double precision.
If you assign the result to a float variable, the value is rounded as necessary. For more details on floating-point math, see “math.h”.
C defines only minimal requirements for the storage size and binary format of the floating-point types. However, the format commonly used is the one defined by the International Electrotechnical Commission (IEC) in the 1989 standard for binary floating-point arithmetic, IEC 60559. This standard is based in turn on the Institute of Electrical and Electronics Engineers’ 1985 standard, IEEE 754. Compilers can indicate that they support the IEC floating-point standard by defining the macro __STDC_IEC_559__. Table 2-6 shows the value ranges and the precision of the real floating-point types in accordance with IEC 60559, using decimal notation.
| Type | Storage size | Value range | Smallest positive value | Precision |
|---|---|---|---|---|
|
4 bytes |
±3.4E+38 |
1.2E-38 |
6 digits |
|
8 bytes |
±1.7E+308 |
2.3E-308 |
15 digits |
|
10 bytes |
±1.1E+4932 |
3.4E-4932 |
19 digits |
The header file float.h defines macros that allow you to use these values and other details about the binary representation of real numbers in your programs. The macros FLT_MIN, FLT_MAX, and FLT_DIG indicate the value range and the precision of the float type. The corresponding macros for double and long double begin with the prefixes DBL_ and LDBL_. These macros, and the binary representation of floating-point numbers, are described in “float.h”.
The program in Example 2-2 starts by printing the typical values for the type float, and then illustrates the rounding error that results from storing a floating-point number in a float variable.
#include <stdio.h>#include <float.h>intmain(){puts("\nCharacteristics of the type float\n");printf("Storage size: %d bytes\n""Smallest positive value: %E\n""Greatest positive value: %E\n""Precision: %d decimal digits\n",sizeof(float),FLT_MIN,FLT_MAX,FLT_DIG);puts("\nAn example of float precision:\n");doubled_var=12345.6;// A variable of type double.floatf_var=(float)d_var;// Initializes the float// variable with the value of d_var.printf("The floating-point number ""%18.10f\n",d_var);printf("has been stored in a variable\n""of type float as the value ""%18.10f\n",f_var);printf("The rounding error is ""%18.10f\n",d_var-f_var);return0;}
The last part of this program typically generates the following output:
The floating-point number 12345.6000000000 has been stored in a variable of type float as the value 12345.5996093750 The rounding error is 0.0003906250
In this example, the nearest representable value to the decimal 12,345.6 is 12,345.5996093750. This may not look like a round number in decimal notation, but in the internal binary representation of the floating-point type, it is exactly representable, while 12,345.60 is not.
C99 supports mathematical calculations with complex numbers. The 1999 standard introduced complex floating-point types and extended the mathematical library to include complex arithmetic functions. These functions are declared in the header file complex.h, and include the trigonometric functions csin(), ctan(), and so on (see Chapter 16).
In the C11 standard, support for complex numbers is optional. The macro__STDC_NO_COMPLEX__ can be defined to indicate that the implementation does not include the header file complex.h.
A complex number z can be represented in Cartesian coordinates as z = x + y × i, where x and y are real numbers, and i is the imaginary unit, defined by the equation i2 = -1. The number x is called the real part, and y the imaginary part, of z.
In C, a complex number is represented by a pair of floating-point values for the real and imaginary parts. Both parts have the same type, whether float, double, or long double. Accordingly, these are the three complex floating-point types:
float _Complex
double _Complex
long double _Complex
Each of these types has the same size and alignment as an array of two float, double, or long double elements.
The header file complex.h defines the macros complex and I. The macro complex is a synonym for the keyword _Complex. The macro I represents the imaginary unit i, and has the type const float _Complex:
#include <complex.h>// ...doublecomplexz=1.0+2.0*I;z*=I;// Rotate z through 90° counterclockwise around the origin
To compose a complex number from its real and imaginary parts, C11 also provides the macros CMPLX, CMPLXF, and CMPLXL. For example, the complex number CMPLX(1.0, 2.0) is equal to the number z defined in the preceding example, and has the type double complex. Similarly, the macros CMPLXF and CMPLXL yield a complex number of the type float complex and long double complex. An implementation may also include the following types to represent pure imaginary numbers: float imaginary, double imaginary, and long double
imaginary.
Enumerations are integer types that you define in a program. The definition of an enumeration begins with the keyword enum, possibly followed by an identifier for the enumeration, and contains a list of the type’s possible values, with a name for each value:
enum [identifier] { enumerator-list };
The following example defines the enumerated type enum color:
enumcolor{black,red,green,yellow,blue,white=7,gray};
The identifier color is the tag of this enumeration. The identifiers in the list—black, red, and so on—are the enumeration constants, and have the type int. You can use these constants anywhere within their scope—as case constants in a switch statement, for example.
Each enumeration constant of a given enumerated type represents a certain value, which is determined either implicitly by its position in the list, or explicitly by initialization with a constant expression. A constant without an initialization has the value 0 if it is the first constant in the list, or the value of the preceding constant plus one. Thus, in the previous example, the constants listed have the values 0, 1, 2, 3, 4, 7, 8.
Within an enumerated type’s scope, you can use the type in declarations:
enumcolorbgColor=blue,// Define two variablesfgColor=yellow;// of type enum color.voidsetFgColor(enumcolorfgc);// Declare a function with a// parameter of type enum color.
An enumerated type always corresponds to one of the standard integer types. Thus, your C programs may perform ordinary arithmetic operations with variables of enumerated types. The compiler may select the appropriate integer type depending on the defined values of the enumeration constants. In the previous example, the type char would be sufficient to represent all the values of the enumerated type enum color.
Different constants in an enumeration may have the same value:
enum{OFF,ON,STOP=0,GO=1,CLOSED=0,OPEN=1};
As the preceding example also illustrates, the definition of an enumerated type does not necessarily have to include a tag. Omitting the tag makes sense if you only want to define constants and not declare any variables of the given type. Defining integer constants in this way is generally preferable to using a long list of #define directives, as the enumeration provides the compiler with the names of the constants as well as their numeric values. These names are a great advantage in a debugger’s display, for example.
The type specifier void indicates that no value is available. Consequently, you cannot declare variables or constants with this type. You can use the type void for the purposes described in the following sections.
A function with no return value has the type void. For example, the standard function perror() is declared by the prototype:
voidperror(constchar*);
The keyword void in the parameter list of a function prototype indicates that the function has no parameters:
FILE*tmpfile(void);
As a result, the compiler issues an error message if you try to use a function call such as tmpfile("name.tmp"). If the function were declared without void in the parameter list, the C compiler would have no information about the function’s parameters, and hence would be unable to determine whether the function call is correct.
A void expression is one that has no value. For example, a call to a function with no return value is an expression of type void:
charfilename[]="memo.txt";if(fopen(filename,"r")==NULL)perror(filename);// A void expression
The cast operation (void)expression explicitly discards the value of an expression, such as the return value of a function:
(void)printf("I don't need this function's return value!\n");
A pointer of type void * represents the address of an object, but not its type. You can use such quasi-typeless pointers mainly to declare functions that can operate on various types of pointer arguments, or that return a “multipurpose” pointer. The standard memory management functions are a simple example:
void*malloc(size_tsize);void*realloc(void*ptr,size_tsize);voidfree(void*ptr);
As Example 2-3 illustrates, you can assign a void pointer value to another object pointer type, or vice versa, without explicit type conversion.
// usingvoid.c: Demonstrates uses of the type void// -------------------------------------------------------#include <stdio.h>#include <time.h>#include <stdlib.h>// Provides the following function prototypes:// void srand( unsigned int seed );// int rand( void );// void *malloc( size_t size );// void free( void *ptr );// void exit( int status );enum{ARR_LEN=100};intmain(){inti,// Obtain some storage space.*pNumbers=malloc(ARR_LEN*sizeof(int));if(pNumbers==NULL){fprintf(stderr,"Insufficient memory.\n");exit(1);}srand((unsigned)time(NULL));// Initialize the// random number generator.for(i=0;i<ARR_LEN;++i)pNumbers[i]=rand()%10000;// Store some random numbers.printf("\n%d random numbers between 0 and 9999:\n",ARR_LEN);for(i=0;i<ARR_LEN;++i)// Output loop:{printf("%6d",pNumbers[i]);// Print one number per loopif(i%10==9)putchar('\n');// iteration and a newline}// after every 10 numbers.free(pNumbers);// Release the storage space.return0;}
Every complete object type imposes a certain alignment on objects of that type. In other words, the type specifies the kind of memory addresses at which objects of that type can be stored: all addresses, only even addresses, only addresses divisible by four, and so on. The alignment of a type is expressed as a number of bytes equal to the minimum distance between two objects of that type in storage. The specific values of the types’ alignments can vary from one implementation to another, but they are always positive integer powers of 2: that is, 1, 2, 4, 8, and so on. An alignment with a greater value than another type’s alignment is said to be stricter than the other.
C11 provides the operator _Alignof to determine a type’s alignment, and the specifier _Alignas to specify the alignment in an object definition.
The _Alignof operator, like the sizeof operator, yields a constant value with the type size_t, an unsigned integer type defined in stddef.h and other header files. For example, the following expression yields the alignment of the type int, which is typically 4:
_Alignof(int)
An alignment value less than or equal to _Alignof(max_align_t) is called a fundamental alignment. All the fundamental types—that is, the basic types and pointer types—have a fundamental alignment. The type max_align_t is defined in the header stddef.h, and its alignment is supported in every context, including dynamic memory allocation, for example. In addition, the implementation may also support alignments greater than _Alignof(max_align_t), which are known as extended alignments.
When an object is defined with the specifier _Alignas, it can have a stricter alignment than its type requires. The argument of _Alignas can be a constant integer expression whose value is a valid alignment, or a type, as in the following examples:
_Alignas(4)shortvar;// Definesvarwith the type short// and four-byte alignment._Alignas(double)floatx;// Definesxwith the type float// and the alignment of double.
The form _Alignas(type) is synonymous with _Alignas (_Alignof(type)). The header file stdalign.h defines alignof and alignas as synonyms for _Alignof and _Alignas. Thus, if your program includes stdalign.h, you can write alignas(int) instead of _Alignas(int).