🌱 C Basics
Variables & types: naming your bytes
▶ Open the interactive lesson — free, no signupThat 2,147,483,647 view ceiling from Part 0 — the one Gangnam Style smashed into — exists because every number a program remembers lives in a box of a fixed size. This lesson is about choosing the right box: you'll give your programs a memory, print what's stored there, and know exactly how big a value can get before it breaks the way YouTube's did.
A program that only prints fixed text is a very expensive poster. Real programs remember things — and in C, memory you can name is a variable. Because C works directly with the machine's memory, every variable has a type that decides two things: how many bytes it occupies (remember from Part 0 — a byte is 8 bits), and how those bytes are interpreted (integer? real number? character?).
Declaring variables
The pattern is type name = value; — the type comes first, then your chosen name, then (optionally, but wisely) an initial value:
#include <stdio.h>
#include <stdbool.h>
int main(void) {
int age = 42; /* whole number */
char grade = 'A'; /* one character (really a */
/* small integer: 65) */
double pi = 3.14159; /* real number */
bool hungry = true; /* 1 or 0 */
unsigned int stars = 4000000000u; /* no negatives, more room */
printf("age = %d\n", age);
printf("grade = %c (which is %d)\n", grade, grade);
printf("pi = %.5f\n", pi);
printf("hungry = %d\n", hungry);
printf("stars = %u\n", stars);
return 0;
}$ gcc variables.c -o variables && ./variables age = 42 grade = A (which is 65) pi = 3.14159 hungry = 1 stars = 4000000000
Uninitialized = garbage. Declaring int x; without a value does NOT give you 0 — a local variable starts with whatever bytes happened to be lying around on the stack. Reading it before assigning is undefined behavior. Initialize your variables.
The integer family
C offers a whole wardrobe of integer types, from small to huge. The keywords short, long, and long long modify int; signed and unsigned choose whether the two's-complement sign bit is used (remember Part 0?):
| type | typical size (x86-64 Linux) | standard guarantees | range (typical) |
|---|---|---|---|
char | 1 byte | exactly 1 byte, ≥8 bits | −128…127 or 0…255 (!) |
short | 2 bytes | ≥16 bits | −32,768…32,767 |
int | 4 bytes | ≥16 bits | ±2.1 billion |
long | 8 bytes (4 on Windows!) | ≥32 bits | ±9.2 × 10¹⁸ |
long long | 8 bytes | ≥64 bits | ±9.2 × 10¹⁸ |
unsigned int | 4 bytes | same size as int | 0…4.29 billion |
Notice the weasel words: the standard only sets minimums. Actual sizes are implementation-defined — chosen by your compiler and platform. Even whether a plain char is signed or unsigned is implementation-defined! When exact sizes matter (file formats, networking), you'll later use int32_t friends from <stdint.h>.
🧠 Checkpoint: You need to count people in a city of 9 million. Which type is the safest minimal choice, guaranteed by the standard?
shortcharintlong
Show answer
long — Careful — the standard only guarantees int ≥ 16 bits (max 32,767)! On your PC an int is 32-bit and would work, but long is guaranteed ≥ 32 bits (±2.1 billion) everywhere. Portable code respects the guarantees, not the typical sizes.
Real numbers and truth values
float— 32-bit IEEE-754, ~7 significant digits.double— 64-bit, ~15–16 digits. The default choice; literals like3.14are doubles._Bool— C99's built-in boolean, holding only 0 or 1. Include<stdbool.h>to spell itboolwithtrue/false— and in C23,bool,true,falseare finally real keywords on their own.
How big is it really? Ask sizeof
The sizeof operator (a real C keyword, evaluated at compile time) tells you the size of any type or expression in bytes:
#include <stdio.h>
#include <stdbool.h>
int main(void) {
printf("char : %zu byte\n", sizeof(char));
printf("short : %zu bytes\n", sizeof(short));
printf("int : %zu bytes\n", sizeof(int));
printf("long : %zu bytes\n", sizeof(long));
printf("long long : %zu bytes\n", sizeof(long long));
printf("float : %zu bytes\n", sizeof(float));
printf("double : %zu bytes\n", sizeof(double));
printf("bool : %zu byte\n", sizeof(bool));
return 0;
}$ gcc sizes.c -o sizes && ./sizes char : 1 byte short : 2 bytes int : 4 bytes long : 8 bytes long long : 8 bytes float : 4 bytes double : 8 bytes bool : 1 byte # your numbers may differ — that's the "implementation-defined" part!
One guarantee is carved in stone: sizeof(char) is always 1 — by definition. Everything else, verify on your platform.
🧠 Checkpoint: Which of these does the C standard actually guarantee?
sizeof(int) == 4sizeof(char) == 1sizeof(long) == 8- plain
charis signed
Show answer
sizeof(char) == 1 — sizeof(char) is 1 by definition — always. Everything else varies: long is 4 bytes on 64-bit Windows but 8 on Linux, and char signedness is the compiler’s choice (it’s unsigned on ARM Linux!).
Variables live at addresses
Each variable claims a chunk of stack memory sized to its type. Here's roughly what the variables from our first example look like in RAM:
▶ This spot has an interactive memgrid widget — open the interactive lesson to play with it.
printf's secret language: format specifiers
printf can't guess types — you must tell it what you're passing with % placeholders:
| specifier | prints a… | example output |
|---|---|---|
%d | int (decimal) | -42 |
%u | unsigned int | 3000000000 |
%ld / %lld | long / long long | 9000000000 |
%c | single character | A |
%f | double (floats are promoted) | 3.141590 |
%.2f | double, 2 decimals | 3.14 |
%zu | size_t (what sizeof yields) | 8 |
%x | int as hex | ff |
%% | a literal % sign | % |
Mismatched specifiers are undefined behavior, not just ugly output. printf("%d", 3.14) tells printf to read an int where a double's bytes sit — garbage or crash may follow. Compile with -Wall and GCC will catch these for you.
▶ This spot has an interactive editor widget — open the interactive lesson to play with it.
🧠 Checkpoint: What does printf("%d", 3.14); do?
- Prints 3
- Prints 3.14
- Undefined behavior — the specifier lies about the type
- Compile error, always
Show answer
Undefined behavior — the specifier lies about the type — printf trusts the format string blindly: %d makes it read an int-sized value where a double was passed. The standard calls this undefined behavior. GCC with -Wall warns; the standard does not require an error.
Variables are lovely, but inert. Time to do things to them: arithmetic operators await.
▶ Practice this lesson interactively (with live gcc)