Memory Usage

Hello World! Here is Switch. Today, we will quickly cover the notion of memory management in computer programs. I will cover notion from pointers to data scope and use program analysis to explain some points.

Introduction to memory management

This post doesn’t aim to cover the overall complexity of memory management witch requires to know how to access specific part of memory, choose between different memory providers and so on. It will instead focus on memory optimisation within a code by ensuring variables are correctly scoped avoid useless duplication.

There are lot of way to manage memory in a program. Nowadays, most langages use the notion of Garbage Collector to ensure memory taken by variables is released when it has no more usage but older langage such as C will require you to do it yourself.

Optimising memory usage through a program requires to correctly identifies the life cycle of values. When do I create it, witch function will use it and when will it be no more used. You also need to be aware of the weight of variables you are going to manipulate. Depending on your performance objectives, passing a full copy of variables to sub function can be a blunder.

Let’s look at this code:

a = 10
b = 15
c = a + b
d = a - b
e = c * d 

There are a lot to say. Though this code is quite simple, it is under optimised. Let’s say we cannot cumulate operation (I cannot directly write (a+b)*(a-b)). We could still save at least a variables.

Variables a and b are used to compute c and d so they need to be alive for c and d but e use c and d, not a and b so we don’t need a new variables here. It makes things more readable but take useless space. We could put c*d response to a or b to save a space. We can make the same analysis for d variables assignation. The last time a and b are used is to compute d. So we could save d value in a or b directly. This would give something like:

a = 10
b = 15
c = a + b
a = a - b
a = c * a 

This kind of optimisation is not required in modern programming langage cause compilers will do it for you, so it is better to keep a clear variable management that will help anyone to get what is going on.

Let’s move on to the main subject.

Variable scopes

The first point to correctly manage memory is to be aware of variable scope. The scope of a variable is the place she is define and the code part it will exist in. We mostly use local variable while coding. A local variable exists only where it is define and until the definer scope is destroyed.

As an example, in this code:

void anotherScope(void) {
  int c, a;
  c = 10;
  a = 4;

  printf("%d", a + c)
}

void scoped(void) {
  int a, b;
  a = 5;
  b = 4;
  
  anotherScope();
  printf("%d", a + b);
}

int main(void) {
  scoped();
  return 0;
}

The variables defined in anotherScope function exists only while it is running. The a variables in scoped and anotherScope are distinct and does not affect each others. This is due to scope isolation. A function call create a scope in witch variables will exists. Only variables defined by the function (entry parameters or locally defines) exists in this scope. If you need to share values between functions with a single name, you have to use global variables. In most langages, variables defined outside of function scope are considered global. Some can require the use of a global key word. If we redefined the previous code with a global a variable:

int a

void anotherScope(void) {
  int c;
  c = 10;
  a = 4;

  printf("%d", a + c)
}

void scoped(void) {
  int b;
  a = 5;
  b = 4;
  
  anotherScope();
  printf("%d", a + b);
}

int main(void) {
  scoped();
  return 0;
}

anotherScope and scoped change a global variable. This change the result. The initial result was

14
9

while it will give

14
8

when using a globally defines a.

Defining a global variables will help saving space when you need to share data between multiple functions around the code but can make it hard to debug issues happening when multiple part of the code modifies it in an unexpected order.

This code:

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

int a

void anotherScope(void) {
  int c;
  c = 10;
  a = 4;

  printf("%d should be 14", a + c)
}

void scoped(void) {
  int b;
  b = 4;

  printf("%d should be 9", a + b);
}

int main(void) {
  a = 5;
  anotherScope();
  scoped();
  return 0;
}

Expect a comportement witch is not respected. Variable a is set to 5 in main and the scoped function expect it to stay at 5. But anotherScope set a to 4 without restoring the older value so when called before scoped, it affects the result and create a bug.

This comportment can lead to very tricky situation in larger code base where a global variables is used and altered by many functions.

Pointers

Another way to save memory is to use Pointers. A pointer is a direct reference to the memory. Instead of managing a value, we manage the place it is stored. An address is a light hexa object so it is quickly lighter than heavy struct. Using pointers instead of direct value will avoid copying data when manipulating the object in a specific function. As pointers are memory reference, you have to release it when you know it will not be used or you lost the tracking. Most of the modern langages does it by themselves relying on a garbage collector who keep track of pointers and variables and delete them when they are not used. But it can lead to some issue in langages where you have to release it by yourself like C.

Let’s manipulate some C pointers using the swap method implementation.

We have a swap method define like:

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

void swap(int a, int b) {
  b = a + b; // get sum of A and B
  a = b - a; // A + B - A = B -> A is now B
  b = b - a; // A + B - B = A -> as A is B, and B is A+B, B is now A
  printf("First value is now: %d and second value %d", a, b); // swap done
}

int main(int argc, char *argv[]) {
  int a, b;

  if (argc != 2) { // check that we received 2 arguments
    printf("Swap expect 2 arguments, %d received!", argc)
    return 1 // return 1 cause we received an unexpected number of arguments witch is an error
  }

  a = atoi(argv[0])
  b = atoi(argv[1])

  swap(a, b)

  return 0
}

This code print the swapped values, but the swap method has no real use as it does nothing to provided values, nor does it provided the swapped couple. Let’s use pointers to modify the provided reference so swap will have a real use.

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

// This method swap a and b values relying on
// a value computed swap
void swapValue(int *a, int *b) {
  *b = *a + *b;
  *a = *b - *a;
  *b = *b - *a;
}

// This method swap the address of a and b
void swapPointers(int **a, int **b) {
  int *c;
  c = *b;
  *b = *a;
  *a = c;
}

int main(int argc, char *argv[]) {
  int a, b;
  int *pa, *pb;

  if (argc != 3) { // check that we received 2 arguments
    printf("Swap expect 2 arguments, %d received!", argc);
    return 1; // return 1 cause we received an unexpected number of arguments witch is an error
  }

  a = atoi(argv[1]);
  b = atoi(argv[2]);
  pa = &a;
  pb = &b;

  swapValue(pa, pb);
  printf("First value is now: %d@%p and second value %d@%p\n", *pa, pa, *pb, pb); // swap done
  swapPointers(&pa, &pb);
  printf("First value is now: %d@%p and second value %d@%p\n", *pa, pa, *pb, pb); // swap done

  return 0;
}

The new swapValue method expect reference to an integer value (int *a indicates that a should be a pointer). It does not modifie the address but directly the value containing by those addresses. The other one, swapPointers, does not modify the values of the pointers but it inverse the pointers to a and pointers to b.

Hope those small example will help you. See you later 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

en_US