When taking the Engineering or CS undergrad path, like 1 plus 1 equals 2 (let's keep it simple here, shall we?), students will take C as their first programming language. Good old TurboC days! Well, C is an amazing programming language, but once they get to face pointers for the first time, hell comes to Earth (for most of them).
Pointers are cool! They allow you to play directly with memory space! But, as uncle Ben used to say,
"with great powers comes great responsibility". With that being said, you should take care when dealing with pointers!
Possible consequences of bad pointers usage can be memory leakage or a incredibly unstable application, just
to name a few examples.
If you are not careful enough, you can leave dangling pointers out there and then chaos is set!
When pointers are not useful anymore, ground them! And remember to free the memory you have asked for! :)
Life will be much simpler that way!
In addition, when dealing with arrays, things can get nasty in terms of how you write the code to access the
contents of the desired pointer. Extra dimensions added to the array will increase the complexity of how you
will write your code to access the memory, especially if you are willing to pass that array to a function.
In this case, you'll want to pass a reference to the array, NOT copy it entirely to the function, as this would
be a huge waste of computational resources.
Let's imagine a single integer:
int x = 10;
We can define a pointer to this integer and make it point to it:
int *ptr_int; /* Pointer to an int */
ptr_int = &x; /* Pointer points to that int (receives the address) */
So, now that your pointer has the address of that int (& operator), it can be dereferenced to give you the contents of that address it points to:
fprintf(stdout,"%d", *ptr_int);
Now, if you think of an array, try to picture that structure in memory. Yes! It is flat! A sequential allocation of positions of the same type. So, in order to create an array of integers, you just need to write:
int array[3] = { 1,2,3 }; /* an array of 3 integers */
How would you declare a pointer for an array of integers? Now C starts playing with its notations. If you thought
of int *ptr[3]; you are wrong! That is not an pointer to an array of integers, but an array of pointers to
integers! So, instead of holding 3 integers, this array holds 3 memory addresses for int values.
To have a pointer to an array of integers, you would simply use a pointer!
int array[3] = { 1,2,3 }; /* an array of 3 integers */
int *ptr; /* Pointer to an 1D-array of integers */
Another cool thing is that when dealing with arrays, they come down to memory addresses (pointers!). So if you want to make the pointer point to that array and use it, there is no need for the & operator at this time, as we are using the address directly:
int array[3] = { 1,2,3 }; /* an array of 3 integers */
int *ptr; /* Pointer to an 1D-array of integers */
/* Makes ptr points to the same location array points to!*/
ptr = array;
/* Using arrays index notation */
fprintf(stdout, "First element: %d\n", ptr[0]); /* Prints 1 */
fprintf(stdout, "First element: %d\n", ptr[1]); /* Prints 2 */
fprintf(stdout, "First element: %d\n", ptr[2]); /* Prints 3 */
/* Using pointers arithmetics notation */
fprintf(stdout, "First element: %d\n", *(ptr + 0)); /* Prints 1 */
fprintf(stdout, "First element: %d\n", *(ptr + 1)); /* Prints 2 */
fprintf(stdout, "First element: %d\n", *(ptr + 2)); /* Prints 3 */
Now, let's think of a Matrix. A Matrix is an array of one or more 1D arrays. The same logic above is used to access its elements. Consider a square 2DArray int 2DArray[2][2] as follows:
int 2DArray[2][2] = { {0,1}, {2,3}};
Its memory representation is linearly consecutive, from the computer's point of view.
2DArray: [0][1][2][3]
That being said, we can use a pointer to an array of ints that will be able to traverse each of the arrays
of the 2DArray, just adding a "new line" after each array, so we can reproduce the visualisation of the 2DArray.
In addition, these are how to point to the above 2DArray elements:
2DArray --> Whole 2D Array
*(2DArray) --> First ROW of the 2D array
Acessing rows:
*(2DArray + 0) --> Same as *(2DArray)
*(2DArray + 1) --> Second ROW of the 2D array
*(2DArray + N) --> Nth ROW of the 2D array
Acessing columns:
*( *(2DArray + 0) + 0) --> Points to 2DArray[0][0]
*( *(2DArray + 0) + 1) --> Points to 2DArray[0][1]
*( *(2DArray + 1) + 0) --> Points to 2DArray[1][0]
*( *(2DArray + 1) + 1) --> Points to 2DArray[1][1]
Here is a sample program to illustrate what we have been talking about throughout this post.
mkdir -p src/{1,2}d
touch src/{{CMakeLists.txt,main.c,includes.h},{1d/1d_arrays.{c,h},2d/2d_arrays.{c,h}}}
CMakeLists.txt:
# Just set policy CMP0054 to NEW to avoid CMP0054 policy violations
cmake_policy(SET CMP0054 NEW)
# Minimum CMake Version
cmake_minimum_required(VERSION 3.10)
# Project name
set(PROJECT_NAME Arrays_Study)
project(${PROJECT_NAME} C CXX)
# Where to find includes
include_directories(
${PROJECT_SOURCE_DIR}/
${PROJECT_SOURCE_DIR}/1d
${PROJECT_SOURCE_DIR}/2d
)
# additional code as shared libs
add_library(1d SHARED 1d/1d_arrays.c)
add_library(2d SHARED 2d/2d_arrays.c)
# the main executable
add_executable(arrays_study main.c)
# links additional code with main target
target_link_libraries(arrays_study 1d 2d)
includes.h:
#ifndef _INCLUDES_H_
#define _INCLUDES_H_
#include
#include
/* Square matrix size */
#define _size 3
/* Beautify arrays printing ;) */
#define _OPEN_CHAR_ '|'
#define _CLOSE_CHAR_ '|'
#endif
main.c:
#include "includes.h"
#include "1d/1d_arrays.h"
#include "2d/2d_arrays.h"
int main
(int argc, char *argv[])
{
/* A 1D array */
int Array1D[_size] = {1,2,3};
/* A 2D array */
int Array2D[_size][_size] = {
{1,2,3},
{4,5,6},
{7,8,9}
};
fprintf(stdout, "Printing a 1D Array\n");
print_1d_array(&Array1D, _size);
fprintf(stdout,"\n");
fprintf(stdout, "Printing a 2D Array\n");
print_2d_array(Array2D, _size);
exit(EXIT_SUCCESS);
}
1d_arrays.h:
#ifndef _1D_ARRAYS_
#define _1D_ARRAYS_
/* Prints 1D arrays
* Args:
* a pointer to an array of ints
* size of that array
*/
void print_1d_array (int (*array)[], int size);
#endif
1d_arrays.c:
#include "../includes.h"
#include "1d_arrays.h"
void print_1d_array
(int (*_array)[], int size)
{
int (*ptr)[size] = _array;
fprintf(stdout, "%c ", _OPEN_CHAR_);
for (int i=0; i < size; i++)
fprintf(stdout, "%d ", (*ptr)[i]);
fprintf(stdout, "%c", _CLOSE_CHAR_);
fprintf(stdout, "\n");
}
2d_arrays.h:
#ifndef _2D_ARRAYS_
#define _2D_ARRAYS_
/* Prints 1D arrays
* Args:
* a pointer to an array of ints
* size of that array
*/
void print_2d_array (int(*_matrix)[], int size);
#endif
2d_arrays.c:
#include "../includes.h"
#include "2d_arrays.h"
void print_2d_array
(int (*_matrix)[], int size)
{
/* Points to the base address of the 2D array */
int (*ptr)[size] = _matrix;
for (int i=0; i < size; i++) {
fprintf(stdout, "%c ", _OPEN_CHAR_);
for (int j=0; j < size; j++ )
/* line i, row j */
fprintf(stdout, "%d ", *( *(ptr + i) + j));
fprintf(stdout, "%c", _CLOSE_CHAR_);
fprintf(stdout, "\n");
}
}
Building the program:
cd src # if not already there
mkdir build
cd build
cmake ../ # of course you'll need cmake, make and gcc
make
When running, it simply prints the two arrays (1D and 2D), but at this point you should understand pointers and arrays :-)
./arrays_study
Printing a 1D Array
| 1 2 3 |
Printing a 2D Array
| 1 2 3 |
| 4 5 6 |
| 7 8 9 |
I hope this can be of any help. -- FIN --