Before Starting:
- basic understanding of memory address & pointers in C:
- 2D arrays in C
- an online IDE with compiler that might come in handy: jdoodle C
What’s the Issue?
Let’s understand the problem first, here’s a C code snippet with bugs.
#include<stdio.h>
void traverse(int **matrix, size_t row_len, size_t col_len){
for (int i = 0; i < row_len; i++){
for (int j = 0; j < col_len; j++){
printf("%d ", matrix[i][j]);
}
printf("\n");
}
}
int main(void) {
int matrix[3][3] = {
{0, 1, 0},
{0, 0, 1},
{0, 1, 0}
};
traverse(matrix, 3, 3);
}
Simply copy and paste and run it. Most likely you will see Command terminated by signal 11
.
Why SIGSEGV Happens?
Now let’s break down these different kinds of “pointers” in C to understand the problem:
- A Normal Pointer (like
int*
):int* ptr
: A pointer in C is a variable that holds the memory address of another variable.- Size of a pointer jump:
ptr++
inint *ptr
moves the pointer bysizeof(int)
bytes.
- Array Name:
int arr[5]
: in this casearr
is not exactly a 100% pointer. As this “pointer” does not allow the increment operation (++). This is because arrays are not modifiable l-values in C.- However, you can use a new pointer
int* ptr = arr
as in C, the name of an array can be used as a pointer to its first element. - Size of a pointer jump:
ptr++
in this case moves the pointer bysizeof(int)
bytes;
- And if it’s a 2D array
int arr[5][5]
:- Then the first element is an
int[5]
array, so a pointerizion in this case should be:int (*ptr)[5] = arr
. - Size of a pointer jump:
ptr++
in this case moves the pointer by5 * sizeof(int)
bytes.
- Then the first element is an
- A Pointer to Pointer:
int ** ptr
: A pointer to pointer in C is a variable that holds the memory address of another pointer.- Size of a pointer jump:
ptr++
inint **ptr
moves the pointer bysizeof(int*)
bytes aka a poniter’s size.
So The function traverse
is expecting a parameter of type int**
, (a pointer to a pointer to an int
). This is different from int (*)[3]
in terms of memory layout and how the pointer arithmetic works.
Please run the following code:
#include<stdio.h>
int main(void) {
int matrix[3][3] = {
{0, 1, 0},
{0, 0, 1},
{0, 1, 0}
};
int **ptr1 = matrix;
int (*ptr2)[3] = matrix;
printf("ptr1: %p\n", ptr1);
printf("ptr2: %p\n", ptr2);
printf("*ptr1: %p\n", *ptr1);
printf("*ptr2: %p\n", *ptr2);
return 0;
}
You can get the output like:
ptr1: 0x7ffe5261a090
ptr2: 0x7ffe5261a090
*ptr1: 0x100000000
*ptr2: 0x7ffe5261a090
Your ptr1
, ptr2
,and *ptr2
might be different than mine (as we run on different machine) but the third line *ptr1: 0x100000000
should be the same and it doesn’t looks like an actual address at all.
When you dereference ptr1
using *ptr1
, you’re treating the value at matrix[0][0]
as a pointer (since ptr1
is an int**
). This is why you get a strange and seemingly unrelated memory address 0x100000000
. It’s interpreting the integer value at matrix[0][0]
as a memory address.
Please notice that operation like int **ptr1 = matrix;
is actually problematic and should not compile without a warning or error in standard C. These are not compatible types. If your compiler allows this assignment, it’s doing so without proper type checking, that is unsafe.
What’s the Solutions?
If your compiler supports variable length arrays (VLA) then you can declare the traverse
function like:
#include <stdio.h>
void traverse(size_t row_len, size_t col_len, int matrix[row_len][col_len]){
for (size_t i = 0; i < row_len; i++) {
for (size_t j = 0; j < col_len; j++) {
printf("%d ", matrix[i][j]);
}
printf("\n");
}
}
int main(void) {
int matrix[3][3] = {
{0, 1, 0},
{0, 0, 1},
{0, 1, 0}
};
printf("matrix first value outside func %d\n", matrix[0][0]);
traverse(3, 3, matrix);
}
This is a cleaner and more straightforward way of handling arrays of varying sizes in C, provided that the compiler supports C99 or later standards.