Arrays are not pointers!
They just happen to be automatically demoted to a pointer to the first element.
For example, if we have int arr[2], then arr represents two consecutive ints in memory, and it gets demoted to a pointer to the first element (so it has type int*):
         arr ---+        +--- arr + 1
                v        v
memory: ... | arr[0] | arr[1] | ...
But if we have int arr[2][2], then arr represents four consecutive ints in memory, and it gets demoted to a pointer to the first element (so it has type int (*)[2]):
           arr ---+            arr + 1 ---+
                  v                       v
memory: ... | arr[0][0] | arr[0][1] | arr[1][0] | arr[1][1] | ...
If, on the other hand, we have int *(arr[2]) (parentheses added for clarity), then arr represents an array of two consecutive int pointers in memory, and it gets demoted to a pointer to the first element (so it has type int**):
         arr ---+        +--- arr + 1
                v        v
memory: ... | arr[0] | arr[1] | ...
                |        |     
           ... -+        +- ...
In your case, print2darr3 expects something like the third example, but you are giving it something like the second.
You can go either way:
#include <stdio.h>
void print2darr3(int rows, int columns, int *arr) {
    int i,j;
    for (i = 0; i < rows ; i++) {
        for (j = 0; j < columns; j++) {
            printf("%d ", arr[i * columns + j]);
        }
        printf("\n");
    }
}
int main(void) {
    int linear_array[3][3] = {
        {10,20,30},
        {40,50,60},
        {70,80,90},
    };
    print2darr3(3, 3, (int*)linear_array);
    return 0;
}
Or:
#include <stdio.h>
void print2darr3(int rows, int columns, int **arr) {
    int i,j;
    for (i = 0; i < rows ; i++) {
        for (j = 0; j < columns; j++) {
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }
}
int main(void) {
    int linear_array[3][3] = {
        {10,20,30},
        {40,50,60},
        {70,80,90},
    };
    int *pointer_array[3] = {
        (int*)(linear_array + 0),
        (int*)(linear_array + 1),
        (int*)(linear_array + 2),
    };
    print2darr3(3, 3, pointer_array);
    return 0;
}
Also, in C (but not in C++) you can do:
#include <stdio.h>
void print2darr3(int rows, int columns, int arr[rows][columns]) {
    int i,j;
    for (i = 0; i < rows ; i++) {
        for (j = 0; j < columns; j++) {
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }
}
int main(void) {
    int linear_array[3][3] = {
        {10,20,30},
        {40,50,60},
        {70,80,90},
    };
    print2darr3(3, 3, linear_array);
    return 0;
}