ZetCode

C data types

last modified November 14, 2026

A type tells the compiler how much memory an object occupies, how its bits are interpreted and which operations are meaningful on it. C has a small set of built-in types, and almost everything else in a program is built from them: an array is a sequence of one type, a structure is a group of several, and a pointer holds the address of one. This tutorial covers the fundamental types, their sizes, signedness, the boolean type and the conversions between them.

The fundamental types

C defines four groups of fundamental types. The integer types are char, short, int, long and long long, each of which exists in a signed and an unsigned variant; char is a separate type whose signedness is up to the implementation. The floating-point types are float, double and long double. _Bool holds 0 or 1, and void is an incomplete type used for functions that return nothing and for generic pointers.

The standard fixes the minimum ranges of these types but not their exact width, so a program that depends on the size of int should check it. The first example prints the sizes on the current platform, in bytes, where one byte is CHAR_BIT bits and CHAR_BIT is at least 8.

sizes.c
#include <stdio.h>

int main(void) {

    printf("char:        %zu\n", sizeof(char));
    printf("short:       %zu\n", sizeof(short));
    printf("int:         %zu\n", sizeof(int));
    printf("long:        %zu\n", sizeof(long));
    printf("long long:   %zu\n", sizeof(long long));
    printf("float:       %zu\n", sizeof(float));
    printf("double:      %zu\n", sizeof(double));
    printf("long double: %zu\n", sizeof(long double));
    printf("_Bool:       %zu\n", sizeof(_Bool));
    printf("int *:       %zu\n", sizeof(int *));

    return 0;
}

sizeof is an operator, not a function, and its result has type size_t, which is why %zu is used. The output shows the common LP64 layout of 64-bit Linux: int is 4 bytes, long and pointers are 8, and long double is 16. On Windows the same program would report 4 bytes for long and 8 for long double, so these numbers are not portable and must never be hard-coded.

$ gcc -std=c17 -Wall -Wextra -o sizes sizes.c
$ ./sizes
char:        1
short:       2
int:         4
long:        8
long long:   8
float:       4
double:      8
long double: 16
_Bool:       1
int *:       8

Characters are small integers

A char occupies one byte and holds a small integer, usually the code of a character in the execution character set. Character literals such as 'A' have type int in C and may be used in arithmetic like any other number.

char_values.c
#include <stdio.h>

int main(void) {

    char letter = 'A';
    signed char small = 127;
    unsigned char byte = 200;

    printf("letter: %c (%d)\n", letter, letter);
    printf("next: %c (%d)\n", letter + 1, letter + 1);
    printf("small: %d\n", small);
    printf("byte: %u\n", byte);

    return 0;
}

Adding 1 to 'A' gives the code of 'B', because the characters are numbered consecutively in ASCII and UTF-8, though the standard does not require that. Whether a plain char can hold 200 depends on the implementation: on x86 it is signed and stops at 127, so the value 200 must be stored in an unsigned char. When the exact range matters, write signed char or unsigned char explicitly.

$ gcc -std=c17 -Wall -Wextra -o char_values char_values.c
$ ./char_values
letter: A (65)
next: B (66)
small: 127
byte: 200

Boolean values

C99 added the type _Bool, which can store only 0 and 1, and the header <stdbool.h>, which defines the alias bool and the constants true and false. Any non-zero value assigned to a _Bool becomes 1.

bool_type.c
#include <stdio.h>
#include <stdbool.h>

int main(void) {

    _Bool flag = 5;      /* any non-zero value becomes 1 */
    bool ready = false;

    printf("flag: %d\n", flag);
    printf("ready: %d\n", ready);
    printf("sizeof(_Bool) = %zu\n", sizeof(_Bool));

    ready = 42;

    printf("ready after 42: %d\n", ready);

    return 0;
}

Assigning 5 stores 1, and assigning 42 to ready stores 1 as well, so a _Bool never holds an arbitrary integer. The type occupies one byte, but only its lowest bit is meaningful. A _Bool passed to printf is promoted to int, which is why %d prints it.

$ gcc -std=c17 -Wall -Wextra -o bool_type bool_type.c
$ ./bool_type
flag: 1
ready: 0
sizeof(_Bool) = 1
ready after 42: 1

Conversions between types

When operands of different types meet in an expression, the compiler converts them to a common type according to the usual arithmetic conversions: the smaller integer types are first promoted to int, and then the wider of the remaining types wins. Assignment in the other direction narrows the value, and for integers that means keeping only the bits that fit.

narrowing.c
#include <stdio.h>

int main(void) {

    int a = 7;
    int b = 2;
    double ratio = a / b;
    double exact = (double) a / b;

    int truncated = 3.99;
    unsigned char wrapped = (unsigned char) 300;

    printf("a / b = %d\n", a / b);
    printf("ratio = %.1f\n", ratio);
    printf("exact = %.1f\n", exact);
    printf("truncated = %d\n", truncated);
    printf("wrapped = %u\n", wrapped);

    return 0;
}

a / b divides two int values, so the result is 3 before it is converted to double; the cast in exact converts a first, which makes the division floating-point. Assigning 3.99 to an int truncates toward zero and gives 3. Converting 300 to unsigned char keeps the low eight bits, and 300 modulo 256 is 44; without the explicit cast, -Woverflow warns about the lost value.

$ gcc -std=c17 -Wall -Wextra -o narrowing narrowing.c
$ ./narrowing
a / b = 3
ratio = 3.0
exact = 3.5
truncated = 3
wrapped = 44

Arrays have a type

An array is a contiguous sequence of elements of one type, so its size in bytes is the number of elements multiplied by the size of one element. That relationship is the portable way to count the elements of an array.

array_size.c
#include <stdio.h>

int main(void) {

    int vals[] = { 10, 20, 30, 40, 50 };

    printf("bytes: %zu\n", sizeof vals);
    printf("element: %zu\n", sizeof vals[0]);
    printf("count: %zu\n", sizeof vals / sizeof vals[0]);

    int *p = vals;

    printf("pointer bytes: %zu\n", sizeof p);

    return 0;
}

The array occupies 20 bytes and each element 4, so the quotient is 5. The pointer p, although it points at the same memory, is itself an object of 8 bytes, and sizeof p reports the size of the pointer and not of the array. This is why a function that receives an array as a parameter cannot use this trick: the parameter is a pointer, and the length must be passed separately.

$ gcc -std=c17 -Wall -Wextra -o array_size array_size.c
$ ./array_size
bytes: 20
element: 4
count: 5
pointer bytes: 8

The type of a literal

Every literal has a type, and a suffix can change it. An integer literal without a suffix is the first type in which its value fits, usually int; a floating literal with a decimal point is a double unless it carries f or L.

literals.c
#include <stdio.h>

int main(void) {

    printf("42:   %zu\n", sizeof 42);
    printf("42L:  %zu\n", sizeof 42L);
    printf("42ULL: %zu\n", sizeof 42ULL);
    printf("3.5:  %zu\n", sizeof 3.5);
    printf("3.5f: %zu\n", sizeof 3.5f);
    printf("3.5L: %zu\n", sizeof 3.5L);
    printf("'A':  %zu\n", sizeof 'A');
    printf("0x2A = %d, 052 = %d\n", 0x2A, 052);

    return 0;
}

The L suffix makes a literal long and ULL makes it unsigned long long; f and L select float and long double. A character constant has type int, so sizeof 'A' is 4 and not 1. The prefixes 0x and a leading 0 select hexadecimal and octal notation, and both spellings of 42 are the same value.

$ gcc -std=c17 -Wall -Wextra -o literals literals.c
$ ./literals
42:   4
42L:  8
42ULL: 8
3.5:  8
3.5f: 4
3.5L: 16
'A':  4
0x2A = 42, 052 = 42

Choosing a type

The rules below cover most decisions. They favor the type that expresses the intent and that is wide enough for every value the variable can take.

Source

cppreference: arithmetic types
cppreference: sizeof operator
cppreference: conversions
cppreference: boolean type

This tutorial covered the data types of C: the integer, floating-point and boolean types, their sizes on a 64-bit platform, characters as small integers, the usual arithmetic conversions and narrowing assignments, the type of an array and of a literal. The exact ranges of the integer types and the fixed-width alternatives are described in C integer limits, and the behavior of the floating-point types in C floating-point types.

Author

My name is Jan Bodnar, and I'm a dedicated programmer with a deep passion for coding. Since 2007, I've been sharing my expertise through over 1,400 articles and 8 e-books. With more than a decade of teaching experience, I strive to make programming accessible and engaging.

List all C tutorials.