Week 12 · Phase 2 — The Ancestry
Telling the RAM how much space you need — and what really happens when you write int x = 5;
Photo · Luke Caunt / Unsplash
You now have a working C program. It can print a fixed string. To do anything more interesting, you need to remember things between lines: a count, a name, a result. You need variables.
In friendly languages, a variable is something you barely think about. x = 5 in Python and you're done — Python figures out the type, allocates space, manages memory, and gets out of your way. C does not get out of your way. C asks you, up front, every time: what kind of value? exactly how many bytes? are you sure?
This is not pedantry. This is the chef asking, "How many cubic centimetres of counter space do you need cleared?" — because the chef is fast and finite, and won't waste a centimetre. By the end of this week you'll know what the chef is asking, and why.
Three things in one line:
int x = 5;
↑ ↑ ↑
type name initial value (optional)
The type tells the compiler "reserve this much space, of this kind, on the stack". The name is what you use to refer to it later — it disappears entirely after the compiler runs. The initial value is what's stored in those bytes. If you leave it out, in C, the bytes contain whatever was there before. Yes, really. (More on that below.)
| Type | Size | Range | What you'd put in it |
|---|---|---|---|
| char | 1 byte | −128 .. 127 | a single character; a small integer |
| short | 2 bytes | ±32 K | small whole numbers |
| int | 4 bytes | ±2 billion | most ordinary integers |
| long | 8 bytes (mostly) | ±9.2 quintillion | big numbers, file sizes |
| float | 4 bytes | ~7 decimal digits | approximate reals: 3.14, sensor readings |
| double | 8 bytes | ~15 decimal digits | precise reals: AI, physics, finance |
| _Bool | 1 byte | 0 or 1 | yes/no flags (added in C99) |
Why these particular sizes? Hardware. Each type maps onto something the chef can manipulate in a single instruction. int is 4 bytes because the chef has 32-bit ADD/SUB/MUL instructions. double is 8 bytes because the floating-point unit operates natively on 64-bit IEEE-754 numbers. Choosing a type is choosing which of the chef's tools you'd like to use.
Caveat: the C standard does not actually fix these sizes. It only requires char ≤ short ≤ int ≤ long ≤ long long. On every machine you'll meet today, the table above is correct — but on a 1985 PC, int was 2 bytes. Modern code that needs an exact size uses int32_t, uint64_t, etc., from <stdint.h>.
Suppose you write:
int x = 5;
char letter = 'A';
double price = 9.99;
The compiler reserves space for these on the stack. They land somewhere like this:
Look closely at the gap between letter and price. That's padding. The compiler inserts it because the CPU prefers (or, on some chips, demands) that 8-byte values start at an 8-byte-aligned address. So price can't start at byte 5; it gets shifted to byte 8, and the bytes in between are just wasted space. This is a real cost: in big structs with mixed-size fields, you can lose 30% of memory to padding if you order them carelessly. Always pack large fields first, small ones last.
What happens if you write this?
int x; // no initial value
printf("%d\n", x); // prints... what?
In Python or Java, the language defaults the variable to zero. In C, the bytes contain whatever was sitting in that memory before. Possibly zero. Possibly a leftover from a previous function. Possibly fragments of a password. The C compiler will (helpfully) warn you about this if you ask it to (-Wall), but the behaviour itself is "undefined" — the standard refuses to say what should happen, and so the chef does whatever's fast.
This single design choice is the source of an enormous fraction of C bugs. It's also one of the things that makes C fast: skipping initialisation saves cycles. Modern compilers can be told to zero-initialise everything by default (-ftrivial-auto-var-init=zero), and most security-sensitive C codebases do exactly that. But the language, by default, will not.
The rule of thumb: always initialise your variables when you declare them. Don't declare an int and write to it three lines later. Decline the foot-gun.
Three concepts you'll inherit from C into every language you ever use:
x and X are different. C reserves keywords (int, return, if) — you can't use those as names.{ } block, only inside that block. Outside any function, it's global. Smaller scopes are better — they reduce the number of places you can corrupt state from.malloc) live until you free them. We'll come back to all three when we hit pointers.#include <stdio.h>
int main(void) {
int apples = 12;
int price_each = 35; // cents
int total = apples * price_each;
double in_dollars = total / 100.0;
printf("%d apples × %d¢ = %d¢ = $%.2f\n",
apples, price_each, total, in_dollars);
return 0;
}
Note a few things. apples and price_each are int, so the multiplication on the next line is integer multiplication — fast, exact, but no fractions allowed. We then divide by 100.0, not 100 — the trailing .0 tells the compiler "treat this as a double", which forces the whole division to use floating-point arithmetic. 100 would have given us integer division (420 / 100 = 4, not 4.20) — a famous beginner trap.
The printf uses format specifiers: %d for integers, %.2f for floating-point with two decimal places. Each one consumes one of the variadic arguments after the format string. Mismatch them — say, %d with a double — and your output is garbage at best, a crash at worst. C trusts you.
Modern AI is, fundamentally, the management of enormous arrays of numbers. A 7-billion-parameter language model has seven billion floating-point variables — each one a tiny float sitting on a GPU. Choosing the type is no longer cosmetic: it's the difference between fitting your model into 14 GB of GPU memory or 28 GB.
This is why, by 2023, the AI world had moved aggressively from float (4 bytes) to __half / bfloat16 (2 bytes), and increasingly to int8 or int4 for inference. Smaller type → smaller variables → smaller model → fits on a smaller GPU. The C lesson — "every variable has a size, and the size matters" — is exactly the lesson that lets a 70-billion-parameter model run on a laptop today.
In C, the type is not a hint. It is a contract with the chef about exactly how many bytes are about to be touched.
Discover your machine's actual sizes and watch padding appear:
#include <stdio.h>
int main(void) {
printf("char = %zu bytes\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("float = %zu bytes\n", sizeof(float));
printf("double = %zu bytes\n", sizeof(double));
return 0;
}
cc -Wall -Wextra hello.c and reading every warning. The compiler is right surprisingly often.struct with fields in different orders. Use sizeof to find its size. Reorder. Watch the size change. You're seeing padding in real time.Variables alone aren't useful — you need to do things with them. Add them, multiply them, compare them, manipulate their bits. That's a small, finite vocabulary, and it's almost identical in every language you'll ever learn.
Week 13 is Operators — the math behind the machine, with all the surprising corner cases of integer overflow, the modulo operator, and why 5 / 2 is sometimes 2 and sometimes 2.5.
Photo free under the Unsplash license. Boxes · Luke Caunt. Memory diagram is inline SVG.