본문 바로가기

Programming/C++ Basic

[Basic C++] 08 - n-Dimensional Array in C++

In this post, I am going to talk about an n-dimensional array in C++. The bottom line of an array in C++ is not as simple as a Python array, however, it's very important to under how the array works in C++ in various programming techniques.


Table of contents

1. one-dimensional array

2. two-dimensional array


1. one-dimensional array

n-dimensional array literally means an array with n dimensions. It is very likely a matrix. Do you remember how to describe and count matrix in Math?. When it comes to a matrix, the number of a row represents its dimension. Therefore, 1-dimensional matrix is 1 x n, and 2-dimensional matrix is 2 x n. n is the number of columns. C++ Array is just like this. an n-dimensional array has n rows and m columns.

 

1.1) how an array is stored in the memory

Array also has a data type and it is stored in the memory like a normal variable. The entire size of array is the sum of memory of each element. If you store four int elements in an array, the total sum of memory is 16 bytes. The syntax of C++ array is below.

 

Data type, identifier, and the number of elements should be defined to declare an array. An array can be initialized too but the elements must be placed in curly brackets. The number of elements also must be compatible with the length of an array. The length must be a constant. There is a special method that you can declare an array with dynamic length, but I will not talk about that here.

 

The elements of an array are stored in memory successively and the first element holds where does it start. (This is a very important concept.) 

Figure 1 Syntax of C++ Array

 

Let's see how arrays are stored in the memory first.

 

Code example 1

#include <iostream>
#include <string>

int main()
{
	int i_ary1[] = { 1, 2};
	int i_ary2[] = { 'a', 'b'};
	char c_ary1[] = { 'a', 'b'};
	char c_ary2[] = { 'a', 'b', '\0'};
	int a_ary[] = { 1, 'a'};

	std::cout << "i_ary1 : "<< i_ary2 << std::endl;
	std::cout << "i_ary2 : "<< i_ary1 << std::endl;
	std::cout << "c_ary1 : "<< c_ary1 << std::endl;
	std::cout << "c_ary2 : " << c_ary2 << std::endl;
	std::cout << "a_ary : "<< a_ary << std::endl;
}

Figure 2 Result of code example 1

 

There are five results and only char arrays showed their actual values. int arrays, on the other hand, showed the memory addresses of the arrays. The size of each array was not defined because the C++ compiler automatically calculates the size from the number of elements.

 

The array 'c_ary1' showed a weird value (ab and some random letters after). This is because the compiler did not know where to end the char array. For int array, it's obvious where an array ends (where is the last element). However, it's not possible for the compiler to differentiate between "ab" (ends with 'b') and "ab are alphabet" (ends with 't'). This is why we need a special letter referring to as "Null character" indicating the end of char array.

 

In addition, if you know how Python arrays work, you might find this really weird. Because in Python, the print function shows you the elements of a given array automatically. Like below, You can easily figure out what elements inside of arrays in Python. However, in C++ you can not do this.

 

// Python Array
i_ary = [1,2]
c_ary = ['a', 'b']
print(i_ary, c_ary)


// Result
[1, 2] ['a', 'b']

Even though I coded "cout << array", MSVC only showed us the "address" of the given arrays. Plus when it comes to "c_ary = ['a', 'b'], it did not even show you the address. Let's see what has happened in memory. Define four breakpoints in your code and debug it. 

 

Figure 3 breakpoints for debugging

 

Turn the memory window (Debug > Window > Memory 1) to see what happens physically in memory and fine i_ary1 (search &i_ary). & (ampersand) means the reference of a variable and we are going to cover it soon. Anyway, if you go to where the a_ary is stored at the end of the four breakpoints, you can see this,

 

Figure 4 memory address

 

You can see the hexadecimal display of each array,

 

int i_ary1 > cc cc cc cc / 01 00 00 00 / 02 00 00 00 / cc cc cc cc - 8 bytes

int i_ary2 > cc cc cc cc / 61 00 00 00 / 62 00 00 00 / cc cc cc cc -  8 bytes

char c_ary1 > cc cc cc cc / 61 62 cc cc / cc cc cc cc - 2 bytes

char c_ary2> cc cc cc cc / 61 62 00 (Null) cc / cc cc cc cc - 2 bytes

int a_ary > cc cc cc cc / 01 00 00 00 / 61 00 00 00 / cc cc cc cc - 8 bytes

 

'CC' bytes are so-called "guard bytes (4 leading, 4 trailing bytes)" which helps programmers to find buffer overflow. Buffer overflow simply means an unwanted memory allocation more than you have wanted to allocate. This is used to prevent unwanted allocation of memory. See the difference between char c_ary1 and c_ary2. There is a null character at the end of c_ary2. I will explain it in the later post.

 

One might wonder why the arrays showed only one address even though they had multiple elements. This is because C++ array returns the address of the first element. Since an array is a group of successively stored data in memory, knowing where the first element is and the length of the array is enough for us.

 

1.2) How to know what is stored in array

Now we know C++ array does not just show you what's inside automatically. It needs some extra steps to see the elements. The syntax  of calling the elements in array is below,

 

Code example 2

#include <iostream>
#include <string>

void what_is_inside(int* ary, int size)
{
	std::cout << "[";
	for (int i = 0; i < size; i++)
	{
		std::cout << ary[i] << ",";
		if (i=size-1)
			std::cout << ary[i];
	}
	std::cout << "]\n";
}

int main()
{
	int i_ary1[] = { 1, 2};
	int i_ary2[] = { 'a', 'b'};
	char c_ary1[] = { 'a', 'b'};
	char c_ary2[] = { 'a', 'b', '\0'};
	int a_ary[] = { 1, 'a'};

	what_is_inside(i_ary1, sizeof(i_ary1)/sizeof(i_ary1[0]));
	what_is_inside(i_ary2, sizeof(i_ary2) / sizeof(i_ary2[0]));
	what_is_inside(a_ary, sizeof(a_ary) / sizeof(a_ary[0]));
}

Figure 5 Result of code example 2

 

A function called "what_is_inside" is defined to show the elements of a given array. As I already mentioned one of the previous posts concerning C++ functions, you can pass values as arguments to functions. However, in C++ you are not allowed to pass arrays as arguments but only the refenreces of arrays are allowed. This way you can save a lot more memory by not copying the same data.

 

In order to get the reference of a variable, you can use an ampersand (&) operator in front of the variable. However, for arrays, you use * (asterisk) operator. It means the identifier is a pointer of the variable. I will tell you about what a pointer in the later post too. Simply, a pointer is just a variable that stores the address of another variable. Thus a pointer itself does not mean anything but points to the target variable.

 

The function "what_is_inside" has one for loop to iterate "cout" to show the elements of the given array. You can call an element of an array using the below syntax.

 

Figure 6 How to get an element of an array

Make sure you 100% know what is the last element in an array. Since the compiler does not know where is the last element located in the given array, your program might fall into an infinite loop or rises a runtime error. (Code example 3).

 

Code example 3

int ary[2] = {1, 2};
std::cout << ary[0];  // 1 
std::cout << ary[1]; // 2
std::cout << ary[2]; // Runtime error

 

1.3) What we've learned

From the overall results now we know,

 

  • First of all, it is pretty obvious that all of the arrays were stored successively in memory. Arrays are right next to each other in chronicle order. This is referred to as "Stack-based memory allocation"; regions of memory where data is added or removed in a Last-in-First-Out (LIFO) manner. I will cover it too later.
  • Second of all, arrays are containers for data and they are the address of real values stored in memory. The address of an array is the address of its first element because an array is a group of values successively stored in memory.
  • Third of all, knowing what elements are stored in an array needs an iteration using a loop statement. array[ index ] syntax returns one element corresponding to the given index, but the last element's index is n-1. (n is the length of an array)

2. n-dimensional array in C++

Now we know what one-dimensional array is. You can also make a multi-dimensional array in C++ easily. Figure 7 shows a two-dimensional array. It has n rows, m columns, and nm elements. They are just like a mathematical matrix. However, make you do not use multidimensional arrays too much because they occupy so much memory. The memory size increases exponentially.

 

Code example 4

#include <iostream>

int main()
{
	int ary1[2];
	int ary2[2][2];
	int ary3[2][2][2];

	int ary4[3];
	int ary5[3][3];
	int ary6[3][3][3];

	std::cout << "ary1 = " << sizeof(ary1) <<" bytes" << std::endl;
	std::cout << "ary2 = " << sizeof(ary2) <<" bytes" << std::endl;
	std::cout << "ary3 = " << sizeof(ary3) <<" bytes" << std::endl;
											 
	std::cout << "ary4 = " << sizeof(ary4) <<" bytes" << std::endl;
	std::cout << "ary5 = " << sizeof(ary5) <<" bytes" << std::endl;
	std::cout << "ary6 = " << sizeof(ary6) <<" bytes" << std::endl;
}

Figure 7 Result of code example 4

Code example 4 well shows the size of each array has increased exponentially. Printing the elements of a multi-dimensional array needs an extra step in a loop.

 

Code example 5 shows us how to get each element in the two-dimensional array. In this case, you need to iterate two loops with i and j for its rows and columns. The functions below were used to print '0' in front of a number.

 

  • std::cout.width(integer); - It defines the width of return values
  • std::cout.fill(string); - It adds an additional string in front of return values if they are shorter than the defined width. 

Code example 5

#include <iostream>
#include <sstream>

void what_is_inside(int ary[][3], int row, int col)
{

	for (int i = 0; i < row; i++)
	{
		std::cout << "[ ";
		for (int j = 0; j < col; j++)
		{
			std::cout.width(2);
			std::cout.fill('0');
			std::cout << ary[i][j] <<" ";
		}
		std::cout << "]\n";
	}
}

int main()
{
	const int row = 3;
	const int col = 3;

	int ary[row][col] = { {00, 01, 02}, {10, 11, 12}, {20, 21, 22} };
	what_is_inside(ary, row , col);
}

Figure 7 Result of code example 5

Look carefully at arguments that were passed to the function. As I told you already, you are only able to pass the address of an array in C++, but the original data. There are three ways to pass the address of an array like below,

 

  • function ( int* ary )
  • function (int ary[] )
  • function (int ary[5] )

When it comes to a multi-dimensional array, you are supposed to give the number of columns to the function because the compiler does not know how many columns there are. We know the address of an array is the address of its first element. The number of rows can be empty.

 

  • function ( int* ary[ ][5] )

 


REFERENCE

 

1) http://www.cplusplus.com/doc/tutorial/arrays/

2) https://en.wikipedia.org/wiki/Stack-based_memory_allocation

3) https://www.tutorialspoint.com/cplusplus/cpp_passing_arrays_to_functions.htm