A Declaration Reserves Bytes

Lesson 1 left you with a picture: a program is instructions and data sitting in memory. So far the data has been baked into the instructions. When you wrote printf("Six sevens are %d\n", 6 * 7), the 42 had nowhere to live. It was computed, printed, and gone.

A variable is how you keep something, and int age = 25; is how you ask for one. That line is a declaration, and it does two jobs. int age reserves a patch of memory big enough to hold an integer and gives that patch a name. The = 25 writes 25 into it. From then on the name age means "those bytes", and the compiler knows both where they are and how to read them.

Memory is only bytes, and bytes carry no label saying what they represent. The type is what decides how many bytes to reserve and how to interpret them, which is why C makes you name one before it will give you a variable.

#include <stdio.h>

int main(void)
{
    int age = 25;

    printf("age is %d\n", age);

    return 0;
}
age is 25

The declaration sits inside main, so the name age is visible only in that block. Every variable in this lesson is local to main in that way, and by the end of the lesson it will matter.

int and double

int holds a whole number, positive or negative, with no fractional part. Reach for it by default when you are counting things.

double holds a real number, one that can have a fractional part: 19.99, -0.5, 72.5. The name is short for "double precision", and the honest description is that it stores a very close approximation of the number you wrote, not always the number itself. There are finitely many bit patterns available and infinitely many real numbers to spell with them, so something has to give. A later chapter deals with the consequences.

Each type has its own conversion specifier. %d prints an int, %f prints a double:

#include <stdio.h>

int main(void)
{
    int students = 30;
    double average_score = 72.5;

    printf("students: %d\n", students);
    printf("average score: %f\n", average_score);
    printf("average score: %.1f\n", average_score);

    return 0;
}
students: 30
average score: 72.500000
average score: 72.5

Plain %f prints six digits after the decimal point, which is rarely what you want. %.1f asks for one and %.2f for two: the number after the dot is the precision.

The specifier has to match the type exactly. Handing a double to %d is undefined behaviour rather than a helpful automatic conversion, and -Wall catches it with the line and column, just as in lesson 1. Variable names, incidentally, are conventionally written in snake_case in C: lowercase words joined by underscores, like average_score.

Assignment Overwrites the Bytes

A variable is not stuck with the value it started with:

#include <stdio.h>

int main(void)
{
    int score = 10;

    printf("score starts at %d\n", score);

    score = 25;
    printf("score is now %d\n", score);

    score = score + 5;
    printf("score is now %d\n", score);

    return 0;
}
score starts at 10
score is now 25
score is now 30

score = 25; names no type, and that is exactly what makes it an assignment rather than a declaration: the variable already exists, so nothing new is reserved. The bytes that held 10 now hold 25. The 10 is not saved, not pushed aside, not recoverable. It was overwritten, the way anything written to memory is overwritten.

score = score + 5; reads oddly if you take = to mean "equals", so do not. It means "work out the right-hand side, then store the result on the left". The right-hand side uses the current contents of score, which is 25, and the resulting 30 is written back over it.

sizeof Tells You How Many Bytes

If a declaration reserves bytes, it is fair to ask how many:

#include <stdio.h>

int main(void)
{
    int age = 25;
    double price = 19.99;

    printf("age is %d and occupies %zu bytes\n", age, sizeof age);
    printf("price is %.2f and occupies %zu bytes\n", price, sizeof price);
    printf("sizeof(int) is %zu\n", sizeof(int));
    printf("sizeof(double) is %zu\n", sizeof(double));

    return 0;
}
age is 25 and occupies 4 bytes
price is 19.99 and occupies 8 bytes
sizeof(int) is 4
sizeof(double) is 8

sizeof is an operator, not a function, even though sizeof(int) looks like a call. The parentheses are required around a type name and optional around a variable, which is why sizeof age works. The compiler works the answer out while compiling, so it costs nothing at run time. Its result has type size_t, the unsigned integer type C uses throughout for sizes and counts, and size_t has its own conversion specifier: %zu. Using %d for it is a type mismatch and -Wall will say so.

Read those numbers as facts about this platform, not about C. The standard guarantees minimum ranges, not exact widths: an int is at least 16 bits, and the 4 bytes you see here is simply what nearly every desktop and server compiler settled on. Never hard-code a size you assumed. Ask sizeof.

Bytes With Nothing Written Into Them

The next program is wrong. It is here so you can see what the compiler does about it.

#include <stdio.h>

int main(void)
{
    int count;

    printf("count is %d\n", count);

    return 0;
}

int count; is a declaration with no initializer. It reserves the bytes and writes nothing into them, so their contents are indeterminate: whatever those bytes happened to be holding already. Reading them is undefined behaviour.

gcc sees it coming:

count.c: In function 'main':
count.c:7:5: warning: 'count' is used uninitialized [-Wuninitialized]
    7 |     printf("count is %d\n", count);
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
count.c:5:9: note: 'count' was declared here
    5 |     int count;
      |         ^~~~~

The same shape of bug report as lesson 1, with a bonus: the note: line points back at the declaration, so you are handed both ends of the mistake.

Now the part that traps people. Run that program three times with this platform's flags and it prints count is 0 all three times, perfectly repeatable. It would be very easy to conclude that C zeroes your variables for you and move on. Compile the same file, not one character changed, with optimization turned off instead, and it prints:

count is 2

The value was never a value. It was whatever those bytes held, and that depends on the compiler, the flags, and what the machine was doing a moment earlier. This is why undefined behaviour is never described as "it crashes" or "it prints garbage": the standard promises nothing, so a result that looks stable proves nothing. Worse, because the read is undefined, the compiler is entitled to assume it never happens and optimize on that assumption, which is how these bugs become behaviour that no amount of staring at the source will explain.

Variables declared inside a function, which is every variable in this lesson, are not zeroed before use. (Variables declared outside any function have a different storage duration and are zero-initialized. That is a later lesson, and it is exactly the contrast people over-generalize from.)

So the rule, which is not a style preference: initialize every variable at the point you declare it. Write int count = 0;. If you do not know the real value yet, zero is a placeholder that is at least a value, which is more than the alternative offers.

A variable, then, is a named, typed, measurable patch of memory. Lesson 3 asks the obvious next question: where exactly in memory, and how would you find out?

Key Takeaways

  • A declaration such as int age = 25; reserves memory big enough for the type, names it, and writes an initial value into it.
  • The type is what gives bytes meaning: it decides how many are reserved and how they are interpreted.
  • int holds whole numbers. double holds real numbers as a close approximation, not always exactly.
  • Print an int with %d and a double with %f, using %.2f style precision to control the decimal places. A mismatched specifier is undefined behaviour and -Wall reports it.
  • Assignment writes over the same bytes. score = score + 5; evaluates the right-hand side first, then stores the result back.
  • sizeof is a compile-time operator, not a function, and its result is a size_t printed with %zu. Sizes like 4 for int and 8 for double are facts about this platform, since C guarantees minimum ranges rather than exact widths.
  • A declaration with no initializer leaves the bytes indeterminate, and reading them is undefined behaviour. A repeatable-looking result is not a guarantee. Initialize every variable at declaration.