Here's an example I've used before. Assume the following two functions:
void foo( T *p ) // where T is an arbitrary data type
{
*p = new_value(); // write new value to the thing p points to
}
void bar( void )
{
T var;
foo( &var ); // write new value to var
}
For function foo to update the contents of var (an object of type T) we must pass a pointer to var (type T *).
If we replace T with a pointer type P *, then the above code becomes
void foo( P **p )
{
*p = new_value(); // write new value to the thing p points to
}
void bar( void )
{
P *var;
foo( &var ); // write new value to var
}
The semantics are exactly the same; we want foo to write a new value to var, so we pass a pointer to var. It's just that in this case, var is already a pointer type, so we wind up passing a pointer to a pointer.
Basically, for foo to update the contents of var, you must pass the expression &var as the argument, meaning the type of the formal parameter p will always have one more level of indirection than the type of var.
Type of var Type of &var Type of p
----------- ------------ ---------
T T * T *
T * T ** T **
T ** T *** T ***
etc.