Command Line Arguments and Dynamic 2D Arrays
Chris Tralie
Table of Contents
Command Line Arguments
Let's consider the following code, which will finally help to demystify a strange declaration of main
with the two parameters argc
and argv
If we compile this program and run it as follows
will output the following
From this we see that argv
is actually a 2D array starting with the name of how the program is actually invoked, and followed by each parameter separated by a space. This is very useful, because now we can run the same program with different inputs from the user via the command line, and we don't need to recompile it every time!
As another example, when we call it as follows (assuming it's in a folder called Week4_DynMemory
):
Two things to notice here:
- We invoked this program with a different command (by going up one directory and then back in), so this shows how the first parameter can vary even if the program is exactly the same
- If we want to include specific arguments that have spaces in them, we need to put them in quotes
One more thing to be mindful of if you use the arguments is that they are all character arrays, which means you will have to do a conversion if you want to treat any of the arguments as numerical values. There are various functions to do this. Below is an example of using the atoi function to convert a character array to an int, which is part of the <stdlib>
library
Now we have a super exciting program that can add for us! If we compile it to a program called adder
and call it with
It will output
Dynamic 2D Arrays
This is a C++ version of Chapter 2.5 Dive Into Systems. The two are very similar, except we can use the new
and free
keywords in C++ to grab memory off the heap, whereas C uses the malloc and free methods, respectively
We've talked about how we can declare 2D array "statically" (with a fixed size at compile time) with syntax like the following 10 x 20 array of ints:
Often, though, we won't know how big an array should be until runtime. In this case, we need to dynamically grab memory from the heap to use. To understand how to do this, we need to understand pointers even more deeply than we already do, but in the process, we will explain how the double pointer type**
actually refers to a 2D array (like argv
for main)
It's easiest to start with a picture of how another option to store a 2D array is as a pointer to pointers
Said differently, the outer array is an array of pointers, so each arr[i]
is of type int*
in this example. Each such element points to the beginning of a different block of memory, which is the inner array stored at this index.
As a little puzzle to help us check our understanding, we can consider how we might use only address addition and pointers dereferencing to access particular elements in an array. As you'll recall, we can describe array access by dereferencing an offset from a pointer
Array Notation | Pointer Notation |
arr[i] |
*(arr + i) |
If we want to do this with a 2D array, we actually have to chase two pointers. First, we need to find the pointer to the beginning of the inner array we want to reach. Then, we move through an offset of that array and finish up the final dereference. It looks like this in the end
Array Notation | Pointer Notation |
arr[i][j] |
*(*(arr + i)+j) |
The *(arr + i)
grabs the correct pointer to the inner array, and then we move to an offset of j
in that inner array to access the element we want.
Mindful of all of this, we can write code to allocate a M x N dynamic array with the following steps:
- Allocate a new array of M pointers
-
For each of the M pointers, allocate a new array array of N values, and make
arr[i]
point to the beginning of this array
Code to do this would look as follows:
This is visually depicted in the following pictures of memory (you should always draw these sorts of pictures if you're confused)
Step 1 |
Step 2 |
After the first step, we have a bunch of "dangling pointers" that aren't pointing to anything meaningful (in fact, they are pointing to random locations) | On the second step, we allocate the inner arrays that the outer pointers point to |
Let's suppose we run the following program to dynamically allocate a 2D array of a specified size
Now let's use valgrind to check to see if we have any memory leaks (dynamic memory we failed to free) when we ask it to allocate a 10x10 array
This will output the following
Oops! We forgot to free our memory! Let's try to add the following line before we return:
Now we get this
Darn! We're still missing some frees on dynamic memory. This is because while we deleted the dynamically allocated pointers on the outside, we never deleted the inner arrays. And actually, once we delete the outer pointers, we have no way to find the inner arrays anymore! So we have to free the inner arrays first.
This also explains why we had lost 480 bytes originally for a 10x10 matrix:
- We had 80 bytes for the outer pointers because each pointer on a 64-bit machine takes up 8 bytes to store an address, and we had 10 of them
- We had 100 total ints in the 10 inner arrays each of 10 elements. Each int is 4 bytes, so this is a total of 400 bytes
In sum, a blueprint for leak free dynamic array allocation and cleanup in C++ is below:
Notice how the inner arrays were the last to be allocated, but they are the first to be deallocated. It is good practice to deallocate things in reverse order to avoid this.
On Your Own: 3D Dynamic Arrays
If you really want to check your pointer understanding, convince yourself that the pointer notation for a 3D dynamic array would be as follows:
Array Notation | Pointer Notation |
arr[i][j][k] |
*(*(*(arr + i)+j)+k) |
And think of how you might dynamically allocate and deallocate an MxNxK array.