In C, a pointer to type T points to a location where some data of type T is stored.  To keep things concrete, I will talk about T = int below.
The most simple use of a pointer could be to point to one value:
int a = 42;
int *pa = &a;
Now, *pa and a are both the same, and equal to 42.  Also, *pa and pa[0] are both equivalent: so, for example, you can do:
*pa += 1; /* a is 43 */
pa[0] += 1; /* a is 44 */
a += 1; /* a is 45 */
In fact, the C compiler translates pa[0] to *(pa+0) automatically.
A pointer can point to a location that is inside a sequence of data:
int arr[] = { 1, 2, 3 }; /* 3 ints */
int *parr = arr; /* points to 1 */
Now, our memory looks like this:
            +---+---+---+
       arr: | 1 | 2 | 3 |
            +---+---+---+
 +------+     |
 | parr | ----+
 +------+
parr is a pointer that points to the first element of arr in the picture above.  Incidentally, parr also has a box around it, because we need to store the object parr somewhere in the memory.  The value of parr is the address of the first element of arr.
Now, we can use parr to access the elements of arr:
arr[0] == parr[0]; /* true */
parr[1]++; /* make arr[1] equal to 3 */
So, pointer can be used to mean, "I point to the first of n elements in a contiguous store of some objects".  Of course, one has to know how many objects there are for this scheme to work: but once we remember to do so, it is an extremely convenient way to access memory in C.
Pointer can be made to point to dynamically allocated memory as well:
#include <stdlib.h>
size_t n;
/* now, obtain a value in n at runtime */
int *p = malloc(n * sizeof *p);
If the malloc() call succeeds above, p now points to the first of a contiguous area allocated for 10 ints.  We can use p[0] through p[n-1] in our program now.
You probably knew most or all of the above :-), but still, the above helps in understanding what I will say next.
Remember we said that a pointer can point to a contiguous sequence of objects of the same type?  The "same type" can be another pointer type as well.
#include <stdlib.h>
int **pp;
pp = malloc(3 * sizeof *pp);
Now, pp points to an int *.  Going back to our earlier picture:
            +------+------+------+
            |      |      |      |
            +------+------+------+
 +------+     |
 |  pp  | ----+
 +------+
And each of the 3 boxes is an int *, which can point to the first element of a contiguous sequence of ints:
for (i=0; i < 3; ++i)
    pp[i] = malloc((i + 1) * sizeof *ppi[i]);
Here, we allocated space for one int in pp[0], 2 in pp[1], and 3 in pp[3]:
             +------+         +---+
 pp -------->|      |-------->|   |
             +------+         +---+---+
             |      |-------->|   |   |
             +------+         +---+---+---+
             |      |-------->|   |   |   |
             +------+         +---+---+---+
So, pp[0] is a pointer to one int, and that int is the only int in a dynamically allocated block of ints.  In other words, pp[0][0] is an int, and points to the top-most "width-3" box above.  Similarly, pp[1][0] and pp[1][1] are both valid and are the two boxes below the pp[0][0] box.
The most common use of pointer to pointer is to create a 2-dimensional "array" at runtime:
int **data;
size_t i;
data = malloc(n * sizeof *data);
for (i=0; i < n; ++i)
    data[i] = malloc(m * sizeof *data[i]);
Now, assuming that all malloc()s succeed, data[0]...data[n-1] are valid int * values, each pointing to a separate length m contiguous int objects.
But, as I showed above, a pointer to pointer need not have the same "number of elements" in each of its "rows".  The most obvious example is argv in main().
By now, as you can guess, a "3-level deep" pointer, such as int ***p; is okay and can be useful in C.