Type of
int ** arr;
has only one valid interpretation. It:
arr is a pointer to a pointer to an integer
If you have more information than the declaration above, that's all you can know about it, that is, if arr is probably initialized, it points to another pointer, which, if possible, is initialized, points to an integer.
Assuming proper initialization, the only guaranteed valid way to use it is:
**arr = 42; int a = **arr;
However, C allows you to use it in several ways.
• arr can be used as a pointer to a pointer to an integer (ie the main case)
int a = **arr;
• arr can be used as a pointer to a pointer to an array of integers
int a = (*arr)[4];
• arr can be used as a pointer to an array of pointers to integers
int a = *(arr[4]);
• arr can be used as a pointer to an array of pointers to arrays of integers
int a = arr[4][4];
In the last three cases, it might look like you have an array. However, the type is not an array. The type is always just a pointer to a pointer to an integer - dereferencing is pointer arithmetic. This is not like a 2D array.
To find out what is appropriate for the current program, you need to look at the initialization of the arr code.
Update
For the updated part of the question:
If you have:
void foo(char** x) { .... };
the only thing you know for sure is that **x will give char and *x will give you a char pointer (in both cases, the correct initialization of x assumed).
If you want to use x different way, for example. x[2] , in order to get the third char pointer, it requires the caller to initialize x , so that it points to a region of memory that has at least 3 consecutive char pointers. This can be described as a contract for calling foo .