Types and how data lives in memory · about 40 min

Variables, declarations and initialization

Why this matters

A program that cannot remember anything can only print fixed text. Variables are how a program stores values it computed or read, so it can use them later. In C, a variable is something very concrete: a few bytes of memory with a type that says how to interpret them. Understanding that concreteness now explains later surprises, such as why an uninitialized variable prints garbage, or why two variables with the same name can coexist.

What a variable is

A variable is a named region of memory that holds a value of a particular type. When you write

int age = 42;

you ask the compiler for enough memory to hold an int (4 bytes on every system you are likely to use), to call it age in your source, and to put the value 42 in it. In memory it might look like this (addresses vary from run to run; these are made up):

  name     address        bytes (an int is 4 of them)
  age      0x7ffd1000     [ 0x2A ][ 0x00 ][ 0x00 ][ 0x00 ]    = 42

Remember from the first lesson that the name age does not exist at run time. The compiler turns every use of age into "the 4 bytes at this address" (or a CPU register). The type int tells it how many bytes and how to read them: as a signed whole number.

Declaring, initializing, assigning

Three related things happen with variables:

int width;          /* declaration: width exists, its type is int, its value is unknown */
int height = 5;     /* declaration with initialization: starts as 5 */
width = 10;         /* assignment: store 10 into an existing variable */
height = height + 1;  /* read height (5), add 1, store 6 back into height */

You can declare several variables of the same type at once, int x = 1, y = 2, z;, but one per line is clearer and makes it obvious which ones were initialized.

A small program that uses variables:

#include <stdio.h>

int main(void)
{
    int width = 12;
    int height = 5;
    int area = width * height;
    int perimeter = 2 * (width + height);

    printf("area = %d\n", area);
    printf("perimeter = %d\n", perimeter);

    width = width + 1;
    printf("new width = %d, area is still %d\n", width, area);
    return 0;
}
area = 60
perimeter = 34
new width = 13, area is still 60

The last line shows something important: area was computed once, when its declaration ran. It holds the number 60, not a formula. Changing width afterwards does not change area.

Names

An identifier (a name for a variable, function or type) is made of letters, digits and underscores, may not start with a digit, and is case-sensitive: count, Count and COUNT are three different names. Keywords such as int, return, if, while and for are reserved. C programmers usually write variables in lower_case_with_underscores; you will also see camelCase. Choose names that say what the value means: seconds_elapsed, not s or temp2.

Uninitialized variables

What does a local variable contain if you never give it a value?

#include <stdio.h>

int main(void)
{
    int count;
    printf("%d\n", count);
    return 0;
}
$ gcc -std=c17 -Wall -Wextra -g -O0 -o prog main.c
main.c: In function 'main':
main.c:6:5: warning: 'count' is used uninitialized [-Wuninitialized]
    6 |     printf("%d\n", count);
      |     ^~~~~~~~~~~~~~~~~~~~~
main.c:5:9: note: 'count' was declared here
$ ./prog
32767

The 32767 is not chosen by anyone. The memory for count was used for something else before (perhaps by the startup code that ran before main), and C does not clear it, because clearing costs time and C never does work you did not ask for. The value is indeterminate; using it is a bug, and in some cases undefined behavior. Your run may print a different number, or 0, which is worse because it hides the bug.

Warning: Always initialize local variables when you declare them, even if only to 0. It costs nothing measurable and removes a whole class of bugs.

Scope: where a name is visible

A variable declared inside a block { ... } exists from its declaration to the closing brace of that block. This region is its scope. Blocks can nest, and an inner block may declare a name that already exists outside, which shadows (hides) the outer one until the inner block ends:

#include <stdio.h>

int main(void)
{
    int x = 5;
    {
        int x = 99;               /* a different variable, also called x */
        printf("inner x = %d\n", x);
    }
    printf("outer x = %d\n", x);
    return 0;
}
inner x = 99
outer x = 5

Two separate variables, two separate pieces of memory. Shadowing is legal but confusing; avoid it in real code (gcc's -Wshadow warns about it). Blocks become really useful with if and loops in the control-flow module.

const: a variable that must not change

Adding const to a declaration makes the variable read-only after initialization:

const int days_per_week = 7;
days_per_week = 8;   /* error */
main.c:6:19: error: assignment of read-only variable 'days_per_week'

Use const for values that should never change. It documents intent and lets the compiler catch accidental writes. (const becomes much more important with pointers.)

Common mistakes

Recap

Going further: compile the shadowing example with -Wshadow added to the gcc command and read the warning. Then look up the full list of C keywords: there are 44 in C17's syntax including the underscore-prefixed ones like _Bool.

Exercises

Exercise 1 · quiz

Legal names

Which of these are valid C variable names? Select all that apply.

Answer
  • total_price

  • _count

  • numItems

An identifier is letters, digits and underscores, and must not start with a digit. 2nd_place starts with a digit, return is a keyword (reserved by the language), and max-value is parsed as max minus value. _count is legal, but names starting with an underscore are partly reserved for the compiler and C library, so avoid them in your own code.

Exercise 2 · write code

Swap two variables

The starter has a = 3 and b = 7. Swap their values using a third variable, so that the existing printf prints exactly:

a = 7, b = 3

Do not change the initial values or the printf, and do not just print the numbers in a different order: the variables themselves must be swapped (the check looks for a = b; or similar assignments, not a rewritten printf).

File: main.c

Starting point

#include <stdio.h>

int main(void)
{
    int a = 3;
    int b = 7;

    /* swap a and b here */

    printf("a = %d, b = %d\n", a, b);
    return 0;
}

Example

expected output

a = 7, b = 3
Hint 1

If you write a = b; first, the old value of a is gone for good.

Hint 2

Save a's value in a new variable first: int tmp = a;

Hint 3

Then a = b; and b = tmp;

Reference solution
#include <stdio.h>

int main(void)
{
    int a = 3;
    int b = 7;

    int tmp = a;
    a = b;
    b = tmp;

    printf("a = %d, b = %d\n", a, b);
    return 0;
}

Exercise 3 · quiz

Uninitialized

What does this program print?

#include <stdio.h>

int main(void)
{
    int count;
    printf("%d\n", count);
    return 0;
}
Answer
  • Unpredictable: the value is indeterminate and reading it is a bug

A local variable without an initializer holds whatever bytes were already in that memory. It might print 0, 32767 or anything else, and may differ between runs, compilers and optimization levels. gcc warns "'count' is used uninitialized" with -Wall. Always initialize locals when you declare them. (Global variables are the exception: they start at zero. You will see why in the functions module.)

Exercise 4 · write code

Rotate three values

Three runners finish a race in the order stored in first, second and third (by bib number). A judge decides the result must be rotated: the runner in first place moves to third, second moves to first, and third moves to second.

Rearrange the values using assignments and one extra variable so the program prints exactly:

first = 22, second = 33, third = 11

Do not change the initial values or the printf.

File: main.c

Starting point

#include <stdio.h>

int main(void)
{
    int first = 11;
    int second = 22;
    int third = 33;

    /* rotate here */

    printf("first = %d, second = %d, third = %d\n", first, second, third);
    return 0;
}

Example

expected output

first = 22, second = 33, third = 11
Hint 1

Which value would be lost by the first assignment you make? Save that one.

Hint 2

Save first in tmp, then shift the others along: first = second; second = third;

Hint 3

Finally put the saved value in third: third = tmp;

Reference solution
#include <stdio.h>

int main(void)
{
    int first = 11;
    int second = 22;
    int third = 33;

    int tmp = first;
    first = second;
    second = third;
    third = tmp;

    printf("first = %d, second = %d, third = %d\n", first, second, third);
    return 0;
}