The C Path — learn C, visually

🌱 C Basics

Variables & types: naming your bytes

⏱ 15 min · free interactive lesson · quizzes, visualizations & a real compiler

▶ Open the interactive lesson — free, no signup
Why you're learning this

That 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:

variables.c
#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;
}
terminal
$ 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?):

typetypical size (x86-64 Linux)standard guaranteesrange (typical)
char1 byteexactly 1 byte, ≥8 bits−128…127 or 0…255 (!)
short2 bytes≥16 bits−32,768…32,767
int4 bytes≥16 bits±2.1 billion
long8 bytes (4 on Windows!)≥32 bits±9.2 × 10¹⁸
long long8 bytes≥64 bits±9.2 × 10¹⁸
unsigned int4 bytessame size as int0…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?

  • short
  • char
  • int
  • long
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

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:

sizes.c
#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;
}
terminal
$ 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) == 4
  • sizeof(char) == 1
  • sizeof(long) == 8
  • plain char is signed
Show answer

sizeof(char) == 1sizeof(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:

specifierprints a…example output
%dint (decimal)-42
%uunsigned int3000000000
%ld / %lldlong / long long9000000000
%csingle characterA
%fdouble (floats are promoted)3.141590
%.2fdouble, 2 decimals3.14
%zusize_t (what sizeof yields)8
%xint as hexff
%%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)