Polyglot
Languages C types
C § types

Types

The C type system is small and concrete. Every object has a static type, fixed at the point of declaration; every expression has a type that the compiler can determine without runtime support. The type system is closely tied to the underlying machine: the integer types correspond to natural-sized machine words, the floating-point types correspond to the formats supported by the floating-point hardware, and the standard does not specify exact widths for most arithmetic types — only minima. A program that depends on an int being thirty-two bits is not portable to implementations on which it is sixteen.

The standard organises the types into a hierarchy: scalar types (integer, floating, pointer, enumeration), aggregate types (array, struct), the union type, the function type, and the void type. The arithmetic conversions, the rules for type compatibility, and the strict aliasing constraints all derive from this hierarchy.

Integer types

The standard integer types come in five categories — char, short, int, long, and long long — each available in signed and unsigned forms. The minimum widths required by the standard are:

TypeMinimum widthTypical 64-bit width
char88
short1616
int1632
long3264 (Unix) / 32 (Windows)
long long6464

The exact widths are implementation-defined and accessible through <limits.h> (INT_MAX, INT_MIN, and so on). Code that needs specific widths uses the fixed-width aliases from <stdint.h> introduced in C99: int8_t, uint16_t, int32_t, uint64_t, and so on. Two further families are available: int_least32_t (the smallest type at least 32 bits wide) and int_fast32_t (the fastest type at least 32 bits wide on the implementation).

#include <stdint.h>
#include <stdio.h>

int main(void) {
    int32_t exact = 1 << 30;
    uint64_t big   = (uint64_t)1 << 50;
    printf("%d %llu\n", exact, (unsigned long long)big);
}

The signedness of plain char is implementation-defined; it is a separate type from both signed char and unsigned char, although it has the representation of one of them. Code that performs arithmetic on character values should use unsigned char to avoid sign-extension surprises.

Boolean and void

C99 introduced _Bool (and bool via <stdbool.h>) as a true Boolean type holding only 0 or 1. Conversion of any scalar value to _Bool yields 0 if the value is zero and 1 otherwise. C23 promoted bool, true, and false to keywords; the <stdbool.h> header remains available for compatibility.

The void type is incomplete and has no values. It serves three purposes: as a function return type indicating that nothing is returned, as the parameter type of a function taking no arguments (int f(void)), and as the pointed-to type of void *, the generic pointer used to express “pointer to an object of unspecified type”.

Floating-point types

The arithmetic floating types are float, double, and long double. The implementation is required to support each, and on most contemporary platforms they correspond to the IEC 60559 (IEEE 754) binary32, binary64, and an extended precision format respectively. Constants and limits are exposed through <float.h> (FLT_MAX, DBL_EPSILON). The behaviour of floating-point arithmetic — rounding modes, exception flags, signalling NaNs — is governed by <fenv.h> and the FENV_ACCESS pragma.

C99 added the complex types float _Complex, double _Complex, and long double _Complex, with the <complex.h> header providing complex, I, and the relevant arithmetic functions.

Compound types

Four mechanisms compose existing types into new ones.

Arrays

An array type denotes a contiguous sequence of objects of the element type:

int     a[10];           // an array of ten ints
char    s[]    = "hi";   // size deduced: 3 (with the terminating null)
double  m[3][4];         // a two-dimensional array, row-major

The size is part of the type. Most contexts cause an array expression to decay into a pointer to its first element; the principal exceptions are the operand of sizeof, the operand of unary &, and a string literal used to initialise an array. The relationship between arrays and pointers is the subject of Pointers.

Structures

A structure aggregates named members with their own types. Members are stored in declaration order; the implementation may insert padding to satisfy alignment requirements:

struct point {
    double x;
    double y;
};

struct point origin = { 0.0, 0.0 };
struct point p      = { .x = 3.0, .y = 4.0 };   // designated initialisers (C99)
double dx           = p.x - origin.x;

Structures may be assigned, passed by value, and returned by value. They may not be compared with == (the standard does not define structural equality); equality must be defined member by member or via memcmp, the latter being unsafe in the presence of padding.

Unions

A union holds at most one of its members at a time; all members occupy the same storage:

union word {
    uint32_t whole;
    uint8_t  bytes[4];
};

Reading a member other than the one most recently written is type punning; the C standard permits this in restricted circumstances (the rule is in §6.5.2.3p3 footnote 95 and §6.5p7). The portable, well-specified way to reinterpret bytes is via memcpy.

Enumerations

An enumeration type declares a set of named integer constants:

enum direction { NORTH, EAST, SOUTH, WEST };

enum direction d = SOUTH;
int            i = SOUTH;        // legal: enum values convert to int

The constants are of type int (until C23, which permits a fixed underlying type). Enumeration is principally a notational convenience; the compiler does not check that values assigned to a variable of enumerated type belong to the declared set.

Type qualifiers

Three qualifiers refine an existing type:

  • const T — values of type T that may not be modified through the qualified lvalue.
  • volatile T — accesses must occur in the source-code order; the compiler may not eliminate or reorder them.
  • restrict T * — pointers carrying the assertion that, for the duration of the access, the object they designate will be accessed only through pointers based on this one.

Qualifiers compose; they may be applied at any level of a derived type:

const int          ci      = 42;            // ci is a const int
const int *        cip     = &ci;           // pointer to const int
int * const        ipc     = &i;            // const pointer to int
const int * const  cipc    = &ci;           // const pointer to const int

A pointer-to-non-const cannot be assigned from a pointer-to-const without a cast; this is the standard’s mechanism for preventing inadvertent modification.

Type aliases

typedef introduces an alias for an existing type:

typedef unsigned long  size_t;
typedef struct point   point_t;
typedef int            (*comparator)(const void *, const void *);

Conventionally, type aliases that name a struct or function pointer are common; aliases that hide a pointer (typedef something *handle) are discouraged because they obscure the level of indirection at the call site.

Conversions

Implicit conversions occur in three principal contexts: arithmetic, assignment, and function-argument passing. Two rules govern the arithmetic case.

The integer promotions

In any expression where an integer of a type narrower than int appears as an operand, the value is promoted: if int can represent every value of the original type, the value is converted to int; otherwise it is converted to unsigned int. The promotions apply to char, signed char, unsigned char, short, unsigned short, the bit-field types, and _Bool.

unsigned char a = 200;
unsigned char b = 200;
unsigned char c = a + b;      // a and b are promoted to int; the sum is 400
                              // converting 400 back to unsigned char yields 144

The usual arithmetic conversions

When two operands of arithmetic type appear in a binary expression, both are converted to a common type by the rules of §6.3.1.8:

  1. If either operand is long double, the other is converted to long double.
  2. Otherwise, if either operand is double, the other is converted to double.
  3. Otherwise, if either operand is float, the other is converted to float.
  4. Otherwise, the integer promotions are applied. Then:
    1. If the operands have the same type, no further conversion.
    2. If both are signed or both are unsigned, the operand of lesser rank is converted to the type of the operand of greater rank.
    3. If the unsigned operand has rank greater than or equal to the signed operand’s, the signed operand is converted to the unsigned type.
    4. Otherwise, if the signed type can represent every value of the unsigned type, the unsigned operand is converted to the signed type.
    5. Otherwise, both are converted to the unsigned type corresponding to the signed type.

The chief practical consequence is that mixing signed and unsigned in the same expression can convert the signed value to a large positive number — a frequent source of bugs in size comparisons.

int      i = -1;
size_t   n = 5;
if (i < n) /* never executed: i is converted to size_t, becoming SIZE_MAX */;

Casts

An explicit conversion uses cast syntax:

double pi  = 3.14159;
int    rounded = (int)pi;      // truncation toward zero
char  *p   = (char *)0x1000;   // pointer cast (rarely well-defined)

The four kinds of cast that the standard recognises as well-defined are: between arithmetic types; between pointer types where one is void *; between integer and pointer types of sufficient width (implementation-defined); and the addition of qualifiers. Casts that violate these rules produce undefined behaviour.

Object representation

Every object occupies a contiguous region of bytes. The size of an object of type T is sizeof(T), measured in char units. The standard guarantees sizeof(char) == 1 but the number of bits in a char is CHAR_BIT, which is at least 8 and on every contemporary platform is exactly 8.

Alignment

Most types have a natural alignment requirement: the address at which an object of the type may legally appear. On a typical 64-bit platform, int requires four-byte alignment and double requires eight-byte alignment. Within a structure, padding bytes may be inserted between members to satisfy alignment:

struct example {
    char a;       // 1 byte
                  // 3 bytes of padding
    int  b;       // 4 bytes, aligned to a 4-byte boundary
    char c;       // 1 byte
                  // 3 bytes of trailing padding for array compatibility
};
// sizeof(struct example) == 12 on a typical 32-bit-int platform

C11 introduced _Alignof (with the macro alignof in <stdalign.h>) to query the alignment requirement and _Alignas (alignas) to specify one. C23 promoted both to keywords.

Strict aliasing

The standard restricts which pointer types may legally be used to access a given object. The general rule (§6.5p7) is that an object must be accessed through an lvalue of one of:

  • The declared type of the object.
  • A qualified version of the declared type.
  • A signed or unsigned integer type of the same width.
  • An aggregate or union type containing one of the above.
  • A character type (char, signed char, unsigned char).

Accessing a float object through an int * violates the rule and yields undefined behaviour, even if the bit patterns are equivalent. The cleanly-defined way to reinterpret bytes is memcpy between objects of compatible storage size; the compiler is required to honour the rule.

Type compatibility

Two types are compatible if one can be converted to the other without a cast. Identical types are trivially compatible; for derived types, the rules are recursive: two pointer types are compatible if their pointed-to types are compatible; two array types are compatible if their element types are and either the sizes match or one is unspecified; two structure types are compatible if they have the same name and members in the same order with compatible types.

Compatibility matters chiefly at link time: a function called with one signature in one translation unit and defined with an incompatible signature in another is undefined behaviour, and the standard imposes no diagnostic obligation. The chief defence against this is the consistent use of headers that declare external functions exactly once.