Lecture 23 - Memory III and Functions I

Published

April 8, 2026

Goals

  • Examine how multi-dimensional arrays are stored in memory
  • Start examining how functions are implemented

Multi-dimensional Arrays Continued…

Last time we were setting up to explore how multi-dimensional arrays appear in memory

#define COLS 4
#define ROWS 2

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

    for (int j =0; j < ROWS; j++){
        for (int i =0; i < COLS; i++){
            printf("a[%d][%d]: %p\n",j, i, &a[j][i]);
        }
    }
}

If we run this, we get

a[0][0]: 0x7ffd28a3fb20
a[0][1]: 0x7ffd28a3fb24
a[0][2]: 0x7ffd28a3fb28
a[0][3]: 0x7ffd28a3fb2c
a[1][0]: 0x7ffd28a3fb30
a[1][1]: 0x7ffd28a3fb34
a[1][2]: 0x7ffd28a3fb38
a[1][3]: 0x7ffd28a3fb3c

We can think of this as an array of length 2, where the “value” is the sub-array. So for example, a[0] starts at 0x7ffd28a3fb20 and continues for 64 bytes. Within that, the data is broken into four individual values

The important thing, however, is that the data is all tightly packed together.

Here is an example where we load the values in and print them out again


#include <stdio.h>

#define ROWS 2
#define COLS 4

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

    // load the array
    for (int j =0; j < ROWS; j++){
        for (int i =0; i < COLS; i++){
            a[j][i] = j * COLS + i;
        }
    }

    // print out the array
    for (int j =0; j < ROWS; j++){
        for (int i =0; i < COLS; i++){
            printf("%d ", a[j][i]);
        }
        printf("\n");
    }

}

This outputs

0 1 2 3
4 5 6 7

note that I am taking advantage of the "\n" to keep things on the same line to make it easier to see

Alternative multi-dimensional storage

Java stores multi-dimensional arrays differently

Instead of one large block, Java makes arrays of arrays. The values in the top array are just references to the locations of the others. This provides some flexibility.

  • the sub arrays don’t all have to be the same size
  • we don’t have to reallocate the entire array if one of the sub-arrays has gotten too big

On the flip side, all of those references take up room, and it takes more time to dereference all of them. It is also easier to save out C array data since you can just out the value directly from memory without chasing down references.

Functions

There are still some mysteries about how data is stored in memory (what is going on in the boilerplate we keep skipping? why are the variables put into memory backwards?)

The answers to those are actually wrapped up in how functions are implemented

Here is a simple function to give us something to talk about:

int sum(int x, int y){
  int result;
  result = x + y;
  return result;

}


int main(int argc, char * argv[]){
  int a,b,c;
  a = 1;
  b = 2;
  c = sum(a,b);
}

You will have noticed that our discussion of assembly didn’t include a way to write our own functions any more than it included loops, so we need to make them ourselves (though we are not entirely out in the cold here)

What do we need in order to make a function?

  • we need a way to jump to the block of code where the function is implemented
  • we need to pass arguments
  • we need to be able to create local variables
  • we need to be able to return a value
  • we need to be able to return to where we were when the function was called

Jumping to the function isn’t a big deal – that is basically just a branch

Getting back is a little more difficult, because functions could be called from multiple places. However, since instructions are stored in memory this really just means that we need to save the address to go back to somewhere, which we will call the return address

So that leaves us with a bunch of things that we need to save somewhere

  • return address
  • arguments
  • local variables
  • return value

There are two extra pieces that make this particularly difficult

  • we need to be able to handle a function calling another function – we need to handle all of the above information for that call as well
  • functions need to be reentrant – in other words we need to be able to handle the function being called a second time before its first invocation has completed (essential for recursion)

So, where are we going to store this data? Some of it can travel in registers, but register space is limited, and we have to handle those multiple function calls

That leaves us with storing (most) of this in memory.

The interesting thing about this block of data is that while we are running the function it will be very important, but as soon as the function is done we don’t care about it at all.

So we will be building something where we add a piece of data on the end for every new function call that is started, and then removing it when the function is ultimately finished. This sounds like a… stack!

That is, in truth, just what we call it. There is a whole section of memory just called The Stack. And the data we are creating and removing is referred to as the stack frame

Mechanical level

vocabulary

Skills