◐ ai-teacher

🔧 C Programming · Chapter 2

Variables, types and how many bytes they take

In Python a variable is a label on a value. In C a variable IS the box — a fixed number of bytes at a fixed address — and the type decides how many.

Study this lesson free in the app →

The basics

A variable in C is a named piece of memory. Its type fixes two things forever: how many bytes it occupies and how those bytes are read — as a whole number, a decimal, or a character.

The problem it solves: the CPU only sees bytes. The type is how you tell the compiler what the bytes mean, so it can pick the right instructions and reserve the right amount of space.

The mental model: rows of numbered lockers. int fare = 250; rents 4 adjacent lockers, writes 250 into them in binary, and calls the group fare. int total = fare; rents 4 more lockers and copies the bytes. Change fare later and total doesn't move. This is the opposite of Python's luggage tags.

Words you must know 4

sizeof
how many bytes a type or variable takes
signed / unsigned
whether negative numbers are allowed
format specifier
%d, %f, %c … tells printf how to read the bytes
uninitialised
declared but never given a value; it holds whatever garbage was there

Explained

int x; — what is x?

In most languages, "nothing yet". In C, it's whatever bytes were left in that memory by the last function that used it. It might be 0 on your laptop and 32767 in production.

Understanding types as bytes is the whole of C. Pointers, arrays, strings and structs are all built on this one idea.

Lockers at a railway station

Chennai Central has a wall of numbered lockers, each exactly one byte wide.

Your ticket says "lockers 1000–1003, read as a whole number". That's the type. The same 4 lockers read as a decimal (float) give a completely different value, because the bytes are identical and the reading is not.

Copying a variable means renting new lockers and copying the contents across. And a locker you rent but never fill still holds whatever the previous traveller left in it.

The types and their sizes

Typical 64-bit Linux/macOS (sizeof in bytes):

| Type | Bytes | Range / use | | --- | --- | --- | | char | 1 | always 1 — one byte, also a small integer | | short | 2 | −32,768 … 32,767 | | int | 4 | ≈ ±2.1 billion | | long | 8 | 4 on Windows — don't rely on it | | long long | 8 | ≈ ±9.2 × 10¹⁸ | | float | 4 | ~7 significant digits | | double | 8 | ~15–16 digits — the default for decimals |

Add unsigned to drop negatives and double the positive range: an unsigned int holds 0 … 4,294,967,295.

The C standard guarantees only minimums, such as "int is at least 16 bits". When the exact size matters (file formats, network data, hashing), use <stdint.h>: int32_t, uint8_t, int64_t. Those are exact.

Ask the machine, don't memorise

#include <stdio.h>
#include <stdint.h>

int main(void) {
    printf("char   %zu\n", sizeof(char));
    printf("int    %zu\n", sizeof(int));
    printf("long   %zu\n", sizeof(long));
    printf("double %zu\n", sizeof(double));
    printf("int64  %zu\n", sizeof(int64_t));

    int fare = 250;
    double km = 12.5;
    char grade = 'A';
    printf("%d %.1f %c %d\n", fare, km, grade, grade);
    return 0;          // last line: 250 12.5 A 65
}

sizeof gives a size_t, and its specifier is %zu. The last printf prints grade twice: as a character (A) and as a number (65). A char is just a 1-byte integer; 'A' is another way to write 65.

What memory actually looks like

int fare = 250;      char g = 'A';      double km = 12.5;

address  1000 1001 1002 1003   1004   1008 ......... 1015
        ┌────┬────┬────┬────┐ ┌────┐ ┌──────────────────┐
bytes   │ FA │ 00 │ 00 │ 00 │ │ 41 │ │  8 bytes, IEEE754 │
        └────┴────┴────┴────┘ └────┘ └──────────────────┘
         └──── fare: int ───┘  g:char      km: double

250 = 0xFA. On x86 and ARM the lowest byte comes first (little-endian), so the bytes read FA 00 00 00.

'A' = 65 = 0x41.

The gap between 1005 and 1007 is padding: the compiler lines up double on an 8-byte boundary. (Exact addresses vary; the shape doesn't.)

The float that wasn't equal

float f = 0.1f;
if (f == 0.1)
    printf("same\n");
else
    printf("different\n");

Gotchas

A variable is a fixed-size box of bytes. The type says how many bytes and how to read them.

= copies the bytes. An uninitialised box holds garbage. When in doubt, sizeof it.

Words to know

sizeofAn operator giving the size in bytes of a type or variable, as a size_t, worked out at compile time.
Signed / unsignedSigned types hold negatives; unsigned types hold only zero and up, with double the positive range.
Format specifierThe %-code in printf/scanf (%d, %f, %c, %zu) that says how to interpret an argument.
InitialisationGiving a variable a value when it is declared: int n = 0;
Indeterminate valueWhat an uninitialised local holds: leftover bytes, not zero.
Little-endianByte order with the least significant byte first in memory, used by x86 and almost all ARM.
<stdint.h>Header with exact-width integer types: int8_t … int64_t, uint8_t … uint64_t.

In two minutes

Test yourself

1. What is sizeof(char)?

  1. Depends on the platform
  2. Always 1
  3. 2, for Unicode
  4. 4
Show the answer

Always 1 — The standard defines sizeof(char) as 1. Every other size is measured in chars. C chars aren't Unicode characters; they're bytes.

2. int a = 5; int b = a; a = 9; What is b?

  1. 9
  2. 5
  3. Undefined behaviour
  4. 0
Show the answer

5 — b got its own copy of a's bytes. Later changes to a don't touch b's memory.

3. Which printf specifier matches a size_t from sizeof?

  1. %d
  2. %zu
  3. %f
  4. %s
Show the answer

%zu — size_t is an unsigned type, and its width varies by platform. %zu is its exact specifier. %d may 'work' on one machine and print garbage on another.

4. You need a value that is exactly 32 bits on every platform (for a binary file header). Which type?

  1. int
  2. long
  3. int32_t
  4. short
Show the answer

int32_t — int and long sizes vary by platform. int32_t from <stdint.h> is exactly 32 bits everywhere it exists.

5. A colleague stores ₹ amounts like 199.99 in a double. What's the best fix?

  1. Use float to save space
  2. Store integer paise (19999) in an int64_t
  3. Round with printf("%.2f")
  4. It's fine as is
Show the answer

Store integer paise (19999) in an int64_t — Binary floating point can't represent most decimal fractions exactly, so sums drift by paise. Integers in the smallest unit are exact. Rounding at print time only hides the drift.

6. int count; count = count + 1; printf("%d", count); (count is a local). What prints?

  1. 1
  2. 0
  3. Unpredictable: count started as garbage
  4. Compile error
Show the answer

Unpredictable: count started as garbage — Locals aren't zeroed. It might print 1 in testing and something else in production. -Wall warns about it.

There are 9 questions, 3 puzzles and 5 recall cards for this lesson in the app — the recall cards come back on later days, just before you would forget.

Learn it so it stays. Read it, test yourself, then recall it on two different days. The app schedules that for you, free.

Start this lesson →