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.
#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.
#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.
#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.
#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.
#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.
#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.
- Use
intfor ordinary counters and small numbers. - Use
size_tfor sizes, counts and array indices, because that is whatsizeofreturns. - Use
long longorint64_twhen the value may exceed the range ofint. - Use
unsignedtypes for bit patterns and flag sets, not for numbers that can never be negative without a reason. - Use
doublefor floating-point calculations;floatonly when memory or precision is deliberately limited. - Use
charfor text andunsigned charfor raw bytes. - Never assume a size: print
sizeofinstead of hard-coding 4 or 8.
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
List all C tutorials.