Say you have the following code:
char *p = strdup("bark");
const char **dp = &p;
Compiling and running with gcc in.c -o out.c -Wall -Wextra -pedantic -Wpedantic:
warning: initialization of ‘const char **’ from incompatible pointer type ‘char **’ [-Wincompatible-pointer-types]
const char **dp = &p;
                  ^
The warning message is clear: the two pointers are of incompatible types. To understand why this is problematic, try and modify the content of p via dp:
char *p = strdup("bark");
const char **dp = &p;
**dp = 'd';
Compile and run with the same command:
warning: initialization of ‘const char **’ from incompatible pointer type ‘char **’ [-Wincompatible-pointer-types]
const char **dp = &p;
                  ^
error: assignment of read-only location ‘**dp’
**dp = 'd';
     ^
The const in const char **dp applies to the content at which *dp is pointing to, namely p (which is non-const). That's why you see the incompatibility warning message.
Now, try and do:
char *p = strdup("bark");
const char **dp = &p;
*dp = strdup("dark");
The code compiles just fine (with the above warning). However, changing the above code to
char *p = strdup("bark");
char *const *dp = &p;
*dp = strdup("dark");
Will produce the following error:
error: assignment of read-only location ‘*dp’
*dp = strdup("dark");
    ^
Unlike in const char **dp, the const in char *const *dp applies to the pointer at which dp is pointing to, namely &p. Therefore, changing it is not allowed.
Note that, in this same case, **dp = 'd'; will compile just fine (with the above warning).
You can go further and try:
char *p = strdup("bark");
const char *const *dp = &p;
*dp = strdup("dark"); // Error
**dp = 'p';           // Error
warning: initialization of ‘const char * const*’ from incompatible pointer type ‘char **’ [-Wincompatible-pointer-types]
const char *const *dp = &p;
                      ^
error: assignment of read-only location ‘*dp’
*dp = strdup("dark");
    ^
error: assignment of read-only location ‘**dp’
**dp = 'x';
     ^
Maybe writing the syntax differently will make things clearer:
- const char **dp == (const char)* *dp
- char *const *dp == const (char*) *dp
- const char *const *dp == const (const char)* *dp