Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.
Data Types in Embedded C: char, int, long, float, double and void Explained
The size, range and signedness of every standard C data type, why those sizes change between ARM, x86 and 8-bit microcontrollers, and how to print and choose each type correctly in firmware.
What You Will Learn
Key Terms
1. What Are Data Types in Embedded C?
Every variable in C has a type. The type tells the compiler three things: how many bytes of memory to reserve, how to interpret the bit pattern stored there, and which machine instructions to use when you operate on it. Understanding data types in embedded C is therefore the foundation for everything that follows: registers, structures, pointers, memory maps and drivers are all built from a handful of basic types.
On a PC, picking a slightly wrong type costs you nothing noticeable. On a microcontroller such as the STM32F407 (192 KB of SRAM, Cortex-M4 core, 32-bit registers) a wrong type can waste scarce RAM, break a hardware register access, or silently give wrong answers when a temperature goes negative. That is why this lesson treats data types in embedded C with much more care than a desktop tutorial would.
int, unsigned char and float exist only in the compiler’s mind; the type you choose decides what those bits mean.2. The Basic Data Types at a Glance
The C language gives you a small set of fundamental types. Everything else (structures, enums, typedefs, pointers) is built from them. The diagram shows how many bytes each type occupies on the STM32F407 when compiled with arm-none-eabi-gcc. Each blue square is one byte.
| Type | Category | Typical use in firmware | Size on Cortex-M4 |
|---|---|---|---|
char | Integer (character) | Text, raw bytes, small values | 1 |
short | Integer | ADC samples, 16-bit registers | 2 |
int | Integer | General counters, loop indexes (native word) | 4 |
long | Integer | Larger counters, timestamps | 4 |
long long | Integer | 64-bit counters, microsecond time | 8 |
float | Floating point | Sensor maths, PID gains (hardware FPU) | 4 |
double | Floating point | Rarely: software-emulated on Cortex-M4 | 8 |
void | No value | No return value, generic pointer void * | n/a |
char is guaranteed to be 1 byte. All other sizes depend on the compiler and CPU. Section 8 explains the consequences.3. The char Type: One Byte, Text and Raw Data
char is the smallest addressable unit in C. By definition sizeof(char) is always 1, and on every mainstream platform that one byte has 8 bits (CHAR_BIT == 8). It serves two jobs: holding a text character ('A' is just the number 65) and holding a small integer or raw byte.
char may be signed or unsigned. GCC on x86 makes it signed; GCC for ARM makes it unsigned. Code that stores 200 in a plain char and then compares c < 0 behaves differently on the two machines. For numbers, use uint8_t or int8_t (Lesson 3.2); reserve plain char for text.Use plain char only for characters and strings. For raw bytes, UART buffers and register-sized fields prefer unsigned char or, better, uint8_t.
4. short, int, long and long long: The Integer Family
C provides four integer sizes beyond char. The standard fixes only their minimum ranges and their order: sizeof(short) ≤ sizeof(int) ≤ sizeof(long) ≤ sizeof(long long). On the STM32F407 (32-bit ARM) the actual values and ranges are:
| Type | Bytes | Signed range | Unsigned range |
|---|---|---|---|
char | 1 | −128 to 127 | 0 to 255 |
short | 2 | −32,768 to 32,767 | 0 to 65,535 |
int | 4 | −2,147,483,648 to 2,147,483,647 | 0 to 4,294,967,295 |
long | 4 | −2,147,483,648 to 2,147,483,647 | 0 to 4,294,967,295 |
long long | 8 | −9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 | 0 to 18,446,744,073,709,551,615 |
Why int matters most
int is defined as the compiler’s “natural” integer, usually the width of a CPU register. On the Cortex-M4 that is 32 bits, so int arithmetic maps directly to single-cycle ADD and SUB instructions. Smaller types such as char and short are automatically widened to int before arithmetic (integer promotion, Lesson 3.3). That is why a plain int loop counter is often both the smallest and the fastest choice on ARM, while uint8_t is the right choice for stored data such as buffers and structure fields.
long versus long long
long is where portability breaks first: it is 4 bytes on ARM and 32-bit systems but 8 bytes on 64-bit Linux and macOS. long long (C99) is at least 64 bits everywhere. On a 32-bit MCU every 64-bit operation takes several instructions (and division calls a library routine), so use long long deliberately, not by habit.
5. float and double: Floating-Point Types
Integers cannot represent fractions. For quantities such as 3.14 or 0.0005 C provides float (32 bit) and double (64 bit), both stored in IEEE-754 format: a sign bit, an exponent and a fraction (mantissa). The value is roughly ±1.fraction × 2exponent.
| float | double | |
|---|---|---|
| Size | 4 bytes | 8 bytes |
| Significant decimal digits | about 6–7 | about 15–16 |
| Largest value | ≈ 3.4 × 1038 | ≈ 1.8 × 10308 |
| Smallest normal value | ≈ 1.2 × 10−38 | ≈ 2.2 × 10−308 |
| Literal suffix | 3.14f | 3.14 (no suffix) |
| STM32F407 hardware | FPv4-SP FPU: single cycle ops | No hardware: software library, 10–50× slower |
2.5 is a double, so x * 2.5 forces the slow software path. Write x * 2.5f, and compile with -mfpu=fpv4-sp-d16 -mfloat-abi=hard.fabsf(a - b) < 1e-5f.6. The void Type: Representing “No Type”
void is the type that has no values. You cannot declare a variable of type void, but it appears in three essential places:
| Use | Example | Meaning |
|---|---|---|
| No return value | void led_toggle(void); | The function returns nothing |
| No parameters | int main(void) | The function takes no arguments (in C, empty () means “unspecified”) |
| Generic pointer | void *p = &x; | A pointer to something of unknown type; needs a cast before dereferencing |
| Discard a result | (void)HAL_Delay(10); | Tells the reader and the compiler the value is deliberately ignored |
Because sizeof(void) is not defined by the standard (GCC allows it as 1), pointer arithmetic on a void * is non-portable. Cast to uint8_t * first, as the example above does.
7. Signed and Unsigned Variants and Default Signedness
All integer types come in a signed and an unsigned form (Module 2 explained the 2’s complement representation behind this). The keywords are signed and unsigned:
| Declaration | Same as | Signedness rule |
|---|---|---|
int x; | signed int x; | Signed by default |
short x; | signed short int x; | Signed by default |
long x; | signed long int x; | Signed by default |
long long x; | signed long long int x; | Signed by default |
unsigned x; | unsigned int x; | int is implied |
char x; | neither of the next two | Implementation-defined: unsigned on ARM, signed on x86 |
signed char x; | – | Always signed (−128..127) |
unsigned char x; | – | Always unsigned (0..255) |
Which one should I use?
- Unsigned for register contents, bit masks, byte buffers, counters that never go below zero, sizes and array indexes.
- Signed for physical quantities that can be negative: temperature offsets, speed error, audio samples.
- Never use plain
charfor numbers. Never compare signed and unsigned values without a cast.
8. Why Data Type Sizes Depend on the Architecture
The C standard was written for machines of many different word sizes, so it does not say “int is 32 bits”. It says only that each type can hold at least a minimum range. As a result, the same data types in embedded C occupy different numbers of bytes on different targets (all values in bytes):
| Platform | char | short | int | long | long long | float | double | pointer |
|---|---|---|---|---|---|---|---|---|
| 8-bit AVR (ATmega) | 1 | 2 | 2 | 4 | 8 | 4 | 4* | 2 |
| 16-bit MSP430 | 1 | 2 | 2 | 4 | 8 | 4 | 8 | 2 |
| ARM Cortex-M4 (STM32F407) | 1 | 2 | 4 | 4 | 8 | 4 | 8 | 4 |
| 32-bit x86 (Linux/Windows) | 1 | 2 | 4 | 4 | 8 | 4 | 8 | 4 |
| 64-bit Linux / macOS (LP64) | 1 | 2 | 4 | 8 | 8 | 4 | 8 | 8 |
| 64-bit Windows (LLP64) | 1 | 2 | 4 | 4 | 8 | 4 | 8 | 8 |
* avr-gcc makes double the same size as float by default.
long counter;The portability problem
Imagine a driver developed on a PC and then ported to the STM32F407. A variable declared int counter; holds 2 billion on ARM but only 32,767 on an 8-bit AVR. A structure that maps a hardware register with long fields would be 4 bytes per field on ARM and 8 bytes on a 64-bit PC, so the layout changes. A %d in printf given a 64-bit argument prints garbage. All of these are silent failures. This is the motivation for the fixed-width types of Lesson 3.2.
| Model | Meaning | Where |
|---|---|---|
| ILP32 | int, long, pointer = 32 bit | ARM Cortex-M, 32-bit x86 |
| LP64 | long, pointer = 64 bit; int = 32 bit | 64-bit Linux, macOS |
| LLP64 | only long long and pointer = 64 bit | 64-bit Windows |
| IP16 | int, pointer = 16 bit | AVR, MSP430 |
9. What the C Standard Guarantees (limits.h and float.h)
Because sizes vary, the standard provides headers that report the actual limits of the compiler you use. <limits.h> covers integers and <float.h> covers floating point. The guaranteed minimum sizes are:
| Type | Minimum bits guaranteed | Minimum signed range | Header macros |
|---|---|---|---|
| char | 8 | −127..127 or 0..255 | CHAR_BIT, CHAR_MIN, CHAR_MAX |
| short | 16 | −32,767..32,767 | SHRT_MIN, SHRT_MAX, USHRT_MAX |
| int | 16 | −32,767..32,767 | INT_MIN, INT_MAX, UINT_MAX |
| long | 32 | −2,147,483,647..2,147,483,647 | LONG_MIN, LONG_MAX, ULONG_MAX |
| long long | 64 | ±9.2 × 1018 | LLONG_MIN, LLONG_MAX, ULLONG_MAX |
UINT16_MAX and friends from <stdint.h> are even better (Lesson 3.2).10. printf Format Specifiers for Each Data Type
When you print values (over UART, SWO/ITM or semihosting) the format specifier must match the type of the argument. A mismatch is undefined behaviour: wrong digits at best, a crash at worst. Here are the specifiers you will use with data types in embedded C:
| Specifier | Argument type | Prints | Example output |
|---|---|---|---|
%d / %i | int | signed decimal | -42 |
%u | unsigned int | unsigned decimal | 4294967295 |
%x / %X | unsigned int | hexadecimal, lower/upper | ff / FF |
%o | unsigned int | octal | 377 |
%c | int (char) | one character | A |
%s | char * | null-terminated string | hello |
%f | double (float promoted) | decimal float | 3.141593 |
%e | double | scientific | 3.141593e+00 |
%g | double | shorter of %f/%e | 3.14159 |
%p | void * | pointer address | 0x20000000 |
%ld / %lu | long / unsigned long | long decimal | 123456 |
%lld / %llu | long long | 64-bit decimal | 9000000000 |
%hd / %hhd | short / char (promoted to int) | narrow decimal | -5 |
%zu | size_t | result of sizeof | 4 |
%% | – | literal percent sign | % |
-u _printf_float; %lld may also be missing. When in doubt, print floats as scaled integers (for example %d.%02d).11. Choosing the Right Type in Firmware
| What you store | Good choice | Why |
|---|---|---|
| Hardware register value | uint32_t (Lesson 3.2) | Exact width, never negative, no sign surprises |
| Byte received over UART/SPI | uint8_t | Exactly 1 byte, unsigned |
| Text character | char | Matches C string functions |
| ADC reading (12 bit) | uint16_t | Fits 0..4095; half the RAM of int |
| Temperature −40..+125 °C | int8_t or int16_t | Needs negative values |
| Loop counter | int or unsigned int | Native word is fastest on ARM |
| Millisecond tick counter | uint32_t | Wraps after 49.7 days; handle with subtraction |
| Microsecond time since boot | uint64_t | 32 bits wrap after only 71 minutes |
| Sensor value with fractions | float | Hardware FPU on Cortex-M4F |
The guiding principle: pick the smallest type that is always large enough, unless the CPU word size makes a larger type faster, and always document the range in a comment.
12. Common Mistakes With C Data Types
int is 32 bits: It is 16 bits on AVR and MSP430. Code that stores 100000 in an int silently breaks when ported.char for numbers: char x = 200; is 200 on ARM and −56 on x86.printf("%d", 3.5f) and printf("%f", 10) are undefined behaviour and print garbage.f suffix: float x = 0.1; works but computes in double and then converts; on a Cortex-M4F this wastes time and code size.==: 0.1 + 0.2 == 0.3 is false in double arithmetic (the sum is 0.30000000000000004). Use a tolerance.uint16_t milliseconds wraps every 65.5 seconds.float avg = sum / count; performs integer division first. Cast: (float)sum / count.int: If the hardware register is exactly 32 bits, use uint32_t so the layout can never change with the compiler.13. Best Practices for Data Types in Embedded C
- Prefer fixed-width types from
<stdint.h>for anything that touches hardware, protocols or memory layout. - Use plain
charonly for text; useuint8_tfor bytes. - Make signedness explicit: unsigned for bits, signed for quantities.
- Use
float(with thefsuffix) on Cortex-M4F; avoiddoubleunless required. - Print
sizeofresults with%zuand fixed-width types with thePRIu32family from<inttypes.h>. - Compile with
-Wall -Wextra -Wconversion -Wformatso mismatches are reported. - Add compile-time size checks for portability-sensitive types, for example
_Static_assert(sizeof(int) == 4, "int must be 32 bits");.
Key Takeaways — Lesson 3.1
- A type fixes the size, the interpretation and the operations of a variable.
- Only
sizeof(char) == 1is guaranteed; every other size depends on the architecture. - On the STM32F407: char 1, short 2, int 4, long 4, long long 8, float 4, double 8, pointer 4 bytes.
- On 64-bit Linux
longis 8 bytes; on 16-bit MCUsintis 2 bytes. This is the portability problem. - Integer types default to signed; plain
charis implementation-defined (unsigned on ARM). floathas hardware support on the Cortex-M4F;doubledoes not.voidmeans no value, no parameters, or a generic pointer.- Use
<limits.h>to query real limits and match eachprintfspecifier to the argument type. - Mastering data types in embedded C leads directly to fixed-width types in the next lesson.
Frequently Asked Questions
Ready to practise?
Next: sizeof output on different platforms, every printf specifier in action, and signed versus unsigned overflow demonstrated with real programs.
Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.

2 Comments