🚀 Alguni Start learning

Unit 11: Grids & Memory

Asking for space, and giving it back.

Unit 11 of 11 in C programming for kids. Its 5 lessons are Grids, Asking for Memory, Chains of Structs, Functions as Values and Memory Master — below is everything each one explains, and a question or two from it to try.

Every sample on this page was compiled and run with gcc before it shipped, and prints exactly what it says it prints.

This unit opens with a fortnight’s trial of everything — no card needed — or with a family plan, bought in the iPhone app. The first two units of every track are free for ever. Try it in the app.

🔲 Grids

An array of arrays

Two sets of brackets make a 2D array — rows and columns. The first number picks the row, the second picks along it.

C

#include <stdio.h>

int main() {
    int grid[2][3] = {{1, 2, 3}, {4, 5, 6}};
    printf("%d\n", grid[0][2]);
    printf("%d\n", grid[1][0]);
    return 0;
}

It prints

3
4

Two loops walk the whole thing

One loop for the rows, another inside it for the columns.

C

#include <stdio.h>

int main() {
    int grid[2][3] = {{1, 2, 3}, {4, 5, 6}};
    for (int r = 0; r < 2; r++) {
        for (int c = 0; c < 3; c++) {
            printf("%d ", grid[r][c]);
        }
        printf("\n");
    }
    return 0;
}

It prints

1 2 3 
4 5 6 

It really is one long row underneath

The rows sit one after another in memory. sizeof shows the whole thing, and dividing gives the number of rows.

C

#include <stdio.h>

int main() {
    int grid[3][4];
    printf("%d\n", (int)sizeof(grid));
    printf("%d\n", (int)(sizeof(grid) / sizeof(grid[0])));
    printf("%d\n", (int)(sizeof(grid[0]) / sizeof(grid[0][0])));
    return 0;
}

It prints

48
3
4

Constants with #define

#define gives a number a name before the program is even compiled. Every SIZE is swapped for 3 — handy when a grid size appears in five places.

C

#include <stdio.h>

#define SIZE 3

int main() {
    int square[SIZE][SIZE];
    for (int r = 0; r < SIZE; r++) {
        for (int c = 0; c < SIZE; c++) {
            square[r][c] = r * SIZE + c;
        }
    }
    printf("%d %d\n", square[0][0], square[2][2]);
    return 0;
}

It prints

0 8

Try it yourself

What does this print?

C

#include <stdio.h>

int main() {
    int g[2][2] = {{1, 2}, {3, 4}};
    int total = 0;
    for (int r = 0; r < 2; r++) {
        for (int c = 0; c < 2; c++) {
            total = total + g[r][c];
        }
    }
    printf("%d\n", total);
    return 0;
}

Answer it in the app

🧱 Asking for Memory

When you do not know the size in advance

An array's size has to be written into the program. malloc asks for space while the program is *running*, and hands back a pointer to it.

C

#include <stdio.h>
#include <stdlib.h>

int main() {
    int *nums = malloc(sizeof(int) * 3);
    nums[0] = 10;
    nums[1] = 20;
    nums[2] = 30;
    printf("%d %d\n", nums[0], nums[2]);
    free(nums);
    return 0;
}

It prints

10 30

Say how many bytes, not how many things

malloc counts in bytes, so it is always sizeof(the type) * how many. Writing the type rather than 4 means the code stays right on any machine.

C

#include <stdio.h>
#include <stdlib.h>

int main() {
    printf("%d\n", (int)(sizeof(int) * 5));
    printf("%d\n", (int)(sizeof(char) * 5));
    return 0;
}

It prints

20
5

malloc leaves it full of rubbish

malloc gives you space, not a clean slate — reading before writing gives whatever was there before. calloc costs a little more and hands back zeros.

C

#include <stdio.h>
#include <stdlib.h>

int main() {
    int *zeros = calloc(3, sizeof(int));
    printf("%d %d %d\n", zeros[0], zeros[1], zeros[2]);
    free(zeros);
    return 0;
}

It prints

0 0 0

After free, the pointer is a lie

The space has gone back, but the pointer still holds the old address. Using it is one of the nastiest bugs in C, because the program often seems fine — for a while.

C

#include <stdio.h>
#include <stdlib.h>

int main() {
    int *p = malloc(sizeof(int));
    *p = 5;
    printf("%d\n", *p);
    free(p);
    p = NULL;
    printf("%d\n", p == NULL);
    return 0;
}

It prints

5
1

Try it yourself

What happens if you never call free?

  • Nothing at all
  • The memory stays taken until the program ends — a leak, which is fatal in something long-running
  • The program will not compile
  • C frees it for you

Answer it in the app

⛓️ Chains of Structs

A struct that points at another of its own kind

This is what malloc makes possible: a list that grows as far as you like. Each node holds a value and the address of the next one.

C

#include <stdio.h>
#include <stdlib.h>

struct Node {
    int value;
    struct Node *next;
};

int main() {
    struct Node *first = malloc(sizeof(struct Node));
    first->value = 1;
    first->next = NULL;
    printf("%d\n", first->value);
    free(first);
    return 0;
}

It prints

1

Joining two together

The last node points at NULL, which is how you know where the chain ends.

C

#include <stdio.h>
#include <stdlib.h>

struct Node { int value; struct Node *next; };

int main() {
    struct Node *a = malloc(sizeof(struct Node));
    struct Node *b = malloc(sizeof(struct Node));
    a->value = 1;
    a->next = b;
    b->value = 2;
    b->next = NULL;
    printf("%d %d\n", a->value, a->next->value);
    free(b);
    free(a);
    return 0;
}

It prints

1 2

Walking to the end

Keep a marker, print it, move it on. Stop when the marker reaches NULL — exactly the pattern from the Python linked-list unit, in a language where you can see the addresses.

C

#include <stdio.h>
#include <stdlib.h>

struct Node { int value; struct Node *next; };

int main() {
    struct Node *a = malloc(sizeof(struct Node));
    struct Node *b = malloc(sizeof(struct Node));
    a->value = 4;
    a->next = b;
    b->value = 6;
    b->next = NULL;

    int total = 0;
    struct Node *at = a;
    while (at != NULL) {
        total = total + at->value;
        at = at->next;
    }
    printf("%d\n", total);

    free(b);
    free(a);
    return 0;
}

It prints

10

Try it yourself

Why must you free the second node before the first?

  • The order does not matter
  • Freeing the first loses the only pointer to the second, and you could never reach it again
  • C insists on it
  • To save memory

Answer it in the app

🎯 Functions as Values

A pointer can point at code

A function has an address too, so a variable can hold *which function to run*. The strange-looking (*op) is what says "this is a pointer to a function".

C

#include <stdio.h>

int add(int a, int b) {
    return a + b;
}

int main() {
    int (*op)(int, int) = add;
    printf("%d\n", op(2, 3));
    return 0;
}

It prints

5

Point it somewhere else and the program changes

The same call line does something different, because the pointer moved.

C

#include <stdio.h>

int add(int a, int b) { return a + b; }
int multiply(int a, int b) { return a * b; }

int main() {
    int (*op)(int, int) = add;
    printf("%d\n", op(3, 4));
    op = multiply;
    printf("%d\n", op(3, 4));
    return 0;
}

It prints

7
12

Handing a function to a function

This is what function pointers are really for. apply does not know or care what it was given — the caller decides.

C

#include <stdio.h>

int twice(int n) { return n * 2; }
int square(int n) { return n * n; }

int apply(int (*f)(int), int n) {
    return f(n);
}

int main() {
    printf("%d\n", apply(twice, 5));
    printf("%d\n", apply(square, 5));
    return 0;
}

It prints

10
25

Try it yourself

What does the name of a function mean without brackets after it?

  • A mistake
  • The address of the function — the function itself rather than its answer
  • Zero
  • The number of parameters

What does this print?

C

#include <stdio.h>

int half(int n) { return n / 2; }

int main() {
    int (*f)(int) = half;
    printf("%d\n", f(9));
    return 0;
}

Answer them in the app

🏆 Memory Master

Try it yourself

What does this print?

C

#include <stdio.h>

int counter(void) {
    static int n = 0;
    n++;
    return n;
}

int main() {
    counter();
    counter();
    printf("%d\n", counter());
    return 0;
}

Answer it in the app