Heap, Stack and Recursion

By Rainer Stropek (@rstropek)

Samples

  • This presentation's samples are written in C and TypeScript
    • You can learn about the differences by comparing them line-by-line
       
  • C is a rather old, established, and system-near programming language
    • C is good for learning to understand how computers work
    • Normally, you do not write C code like the one shown here. Use higher languages like C++, Rust, C#, Java, etc. instead.
       
  • Try all samples online at http://pythontutor.com/ to understand how the different storage types work

Heap and Pointers

Global Variables

#include <stdio.h>
#include <stdbool.h>

// These are "global variables" of different types.
// They are called "global" because they can be used in
// all functions.
double floatingpoint = 4.2;
int integer = 42;
char character = 'a';
bool boolean = true;
struct values { int a, b, c; } v = { 1, 2, 3 };

int main(void) {
  printVariables();
}

void printVariables() {
  printf("Variable values: %f, %d, %c, %d, {%d, %d, %d}", 
    floatingpoint, integer, character, boolean, v.a, v.b, v.c);
}



// These are "global variables" of different types.
// They are called "global" because they can be used in
// all functions.
const floatingpoint: number = 4.2;
const integer: number = 42;
const character: string = 'a';
const boolean: boolean = true;
// JavaScript has no C-like structs

function main(): void {
  printVariables();
}

function printVariables(): void {
  console.log("Variable values: "
    +floatingpoint+', '+integer+', '+character+', '+boolean);
}

main();

Pointers to the Heap

#include <stdio.h>
#define BYTE unsigned char

// A string is in fact a "pointer" to memory on the heap.
char *greeting = "Hello World!";

// An array is in fact a "pointer" to memory on the heap.
BYTE *data;
int i;

// Lets declare a pointer to a structure
typedef struct { double x, y, z; } vector;
vector *v;

int main(void) {
  // Passing a string to a function means passing a pointer.
  puts(greeting);
  
  // Alloc memory on the heap and let v point to it
  v = malloc(sizeof(vector));
  v->x = v->y = v->z = 42.0;
  printf("Vector points to %f / %f / %f", v->x, v->y, v->z);
  free(v); // Free heap memory. v becomes invalid.

  // Alloc array on the heap and initialize it
  data = malloc(3);
  for(i = 0; i < 3; i++) { data[i] = i + 1; }
  free(data); // Free heap memory
}



// JavaScript has a separate data type for strings
const greeting: string = "Hello World!";

// An array is a reference to the heap
let data: number[];
let i;

// Lets declare a reference to an object
interface Vector { x: number; y: number; z: number } 
let v: Vector;

((): void => {
  // Passing a string to a function means passing a reference.
  console.log(greeting);
  
  // Allocate a JavaScript object on the heap
  v = { x: 1, y: 2, z: 3 };
  
  console.log('Vector points to '+v.x+'/'+v.y+'/'+v.z);
  // No need to free memory. Done by JS automatically.

  // Allocate JS array on the heap
  data = []; // No fixed size in JS
  for(i = 0; i < 3; i++) { data.push(i + 1); }
  // No need to free memory. Done by JS automatically.
})();

Pointers in Structures

// Pointers can point to structures
// which contain pointers to structures
// which contain pointers to structures...
typedef struct {
  char *street;
  char *city;
} address;

typedef struct {
  char *firstName;
  char *lastName;
  address *address;
  int age;
} person;
person *p;

int main(void) {
  p = malloc(sizeof(person));
  p->age = 42;
  p->firstName = "Foo";
  p->lastName = "Bar";
  
  p->address = malloc(sizeof(address));
  p->address->street = "Somewhere";
  p->address->city = "Anywhere";
  
  free(p->address);
  free(p);
}
// References can refer to objects
// which contain references to objects
// which contain references to objects...
interface Address {
  street: string;
  city: string;
};

interface Person {
  firstName: string;
  lastName: string;
  address?: Address;
  age: number;
};
let p: Person;

(() => {
  p = {
    age: 42,
    firstName: 'Foo',
    lastName: 'Foo'
  };
  p.address = {
    street: 'Somewhere',
    city: 'Anywhere'
  };
  
  console.log('Done!');
})();

Tree Structure

// Structure can recursively point to elements
// of the same type. Here you see a classic data
// structure in programming: A "tree" structure.
// https://en.wikipedia.org/wiki/Tree_(data_structure)
typedef struct {
  struct node *left;
  struct node *right;
  int value;
} treeNode;
treeNode *leftLeaf;
treeNode *rightLeaf;
treeNode *rootNode;

int main(void) {
  leftLeaf = malloc(sizeof(treeNode));
  leftLeaf->value = 1;

  rightLeaf = malloc(sizeof(treeNode));
  rightLeaf->value = 3;
  
  rootNode = malloc(sizeof(treeNode));
  rootNode->value = 2;
  rootNode->left = leftLeaf;
  rootNode->right = rightLeaf;
  
  free(rootNode->left);
  free(rootNode->right);
  free(rootNode);
}
// Structures can recursively reference elements
// of the same type. Here you see a classic data
// structure in programming: A "tree" structure.
// https://en.wikipedia.org/wiki/Tree_(data_structure)
interface TreeNode {
  left?: TreeNode;
  right?: TreeNode;
  value?: number;
};
let leftLeaf: TreeNode;
let rightLeaf: TreeNode;
let rootNode: TreeNode;

(() => {
  leftLeaf = { };
  leftLeaf.value = 1;

  rightLeaf = { };
  rightLeaf.value = 3;
  
  rootNode = { };
  rootNode.value = 2
  rootNode.left = leftLeaf;
  rootNode.right = rightLeaf;



  console.log(rootNode);
})();

Memory for Strings

#include <stdio.h>
#include <string.h>

char *greeting1;
char *greeting2;

int main(void) {
  // Assigning a string (=pointer) does NOT copy the heap memory.
  // Note: IMMUTABLE string that cannot change.
  greeting1 = "Hello!"; 
  greeting2 = greeting1;
  puts(greeting2);

  greeting1 = greeting2 = NULL;  

  // Allocate heap memory that CAN change (MUTABLE)
  // and copy some text into allocated heap.
  greeting1 = malloc(sizeof(char) * 4);
  strcpy(greeting1, "Hi!");

  // Again, assigning a string (=pointer) does 
  // NOT copy the heap memory.
  greeting2 = greeting1;
  
  // Changing greeting1 ALSO changes greeting2 because 
  // both point to the same memory.
  greeting1[0] = 'h';
  puts(greeting2);
  
  free(greeting1);
}



let greeting1: string;
let greeting2: string;

(() => {
  // JS handles memory for string for you. You do not
  // see the details. String in JS are always IMMUTABLE.
  greeting1 = 'Hello!'; 
  greeting2 = greeting1;
  console.log(greeting2);

  greeting1 = greeting2 = undefined;  

  // JavaScript does NOT have a mutable string


  greeting1 = 'Hi!';



  greeting2 = greeting1;
  
  // If we change greeting1, greeting2 is NOT changing, too.
  
  greeting1 = 'h' + greeting1.substr(1);
  console.log(greeting2);
  
  console.log('Done');
})();

Copying Heap Memory

#include <stdio.h>
#include <string.h>
typedef unsigned char BYTE;

BYTE *values1;
BYTE *values2;
BYTE i;

#define SIZE 3

int main(void) {
  // Allocate array of numbers and write values into it.
  values1 = malloc(SIZE);
  for(i = 0; i < SIZE; i++) { values1[i] = i; }
  
  // Assignment does NOT copy the content of the array
  values2 = values1;
  
  // This time, we want to COPY THE CONTENT of the
  // memory on the heap.
  values2 = malloc(SIZE);
  memcpy(values2, values1, SIZE);
  
  free(values2);
  free(values1);
}




let values1: number[];
let values2: number[];
let i: number;

const SIZE = 3;

(() => {
  // Allocate array of numbers and write values into it.
  values1 = [];
  for(i = 0; i < SIZE; i++) { values1[i] = i; }
  
  // Assignment does NOT copy the content of the array
  values2 = values1;
  
  // This time, we want to COPY THE CONTENT of the
  // memory on the heap.
  
  values2 = values1.slice(); // in modern JS: [...values1]
  

  console.log('Done');
})();

Local Variables on the Stack

Local Variables

#include <stdio.h>

int main(void) {
  // Declare two LOCAL variables. They are only 
  // accessible in "main".
  int a = 21;
  int b = 21;
  
  // The parameters values of a and b are COPIED 
  // onto the stack for "add".
  int sum = add(a, b);
  
  printf("%d + %d = %d", a, b, sum);
}

int add(int a, int b) {
  // Note that a, b, and result are LOCAL variables in "add". 
  // They are stored on the stack.
  int result = a + b;
  return result;
  
  // We do NOT need to free stack memory.
  // This is done automatically.
}


function main(): void {
  // Declare two LOCAL variables. They are only 
  // accessible in "main".
  const a = 21;
  const b = 21;
  
  // The parameters values of a and b are COPIED 
  // onto the stack for "add".
  const sum = add(a, b);
  
  console.log(a + ' + ' + b + ' = ' + sum);
}

function add(a: number, b: number): number {
  // Note that a, b, and result are LOCAL variables in "add". 
  // They are stored on the stack.
  const result = a + b;
  return result;
  
  // We do NOT need to free stack memory.
  // This is done automatically.
}

main();
console.log('Done');

Call by Reference



function main(): void {
  const numbers: number[] = [];
  for (let i = 0; i < 3; i++) { numbers[i] = i; }
    
  // Passing numbers to sum does NOT copy the array,
  // only the reference is copied.
  const result = sum(numbers);
  console.log(result);
  
}

function sum(numbers: number[]): number {
  let result = 0;
  for(let i = 0; i < numbers.length; i++) { result += numbers[i]; }
  
  return result;
}

main();
console.log('Done');


void main() {
  int *numbers = malloc(sizeof(int) * 3);
  for (int i = 0; i < 3; i++) { numbers[i] = i; }
  
  // Passing numbers to sum does NOT copy the array,
  // only the pointer is copied.
  int result = sum(numbers, 3);
  printf("%d", result);
  free(numbers);
}

int sum(int *numbers, int length) {
  int result = 0;
  for(int i = 0; i < length; i++) { result += numbers[i]; }
  
  return result;
}

Block-Scoped Variables (TS)

(() => {
  let a = 1;
  if (1 === 1) {
    // The next "a" is different from the "a" above
    // because it is inside a new block.
    let a = 2;
    if (1 === 1) {
      // The next "a" is different from the "a" above
      // because it is inside a new block.
      let a = 3;
      console.log(a);
    }
    console.log(a);
  }
  console.log(a);
  console.log('Done');
})();

Recursion

Fibonacci Numbers

const maxValue = 13;

function fibonacci(last: number, secondLast: number): void {
    const next = last + secondLast;
    if (next > maxValue) return;

    console.log(next);
    fibonacci(next, last);
}

console.log(1);
console.log(1);
fibonacci(1, 1);
console.log('Done');

Backtracking

How to Learn More?

Heap, Stack and Recursion

By CoderDojo Linz

Heap, Stack and Recursion

Heap, stack and recursion are three important, fundamental principles in computer programming. In this presentation, you learn the basics of the different memory types and explore how recursive algorithms work.

  • 417