Firstly, char **x and char *x[] are not equivalent, except as function parameter declarations.
Equivalent:
int main(int argc, char **argv);
int main(int argc, char *argv[]); // equivalent
Not equivalent:
{
char **pointer;
char *array[3];
}
A C T * pointer can point to a single object of type T, or it can point to any of the elements of an "array of T", and can also point one element beyond the end of such an array.
This information isn't contained in the pointer's language-level, programmer-visible type information.
In some cases a compiler can deduce these situations and provide useful diagnosis if mistakes are made.
A compiler can trace the data flow of where an expression's value came from.
For instance, given:
char *array[3]; /* array of 3 "char *" pointers */
char **ptr = array; /* pointer to array[0] */
Here, the compiler can analyze that ptr was initialized from array. And so between that initialization and any other point in the program, if no assignment to ptr takes place, it can be assumed to still be pointing to array[0].
So it is possible for the compiler to produce a warning for a dubious expression like ptr + 5, while remaining silent for ptr + 3.
ISO C doesn't require any such analysis or diagnostic, though; it's a "quality of implementation" matter.
There are limitations to how much can be diagnosed statically. C functions can receive pointer values as parameters "out of nowhere", about which nothing can be reasonably be known at compile time.
By the way, the declaration style char* ptr is a bad habit; it should be styled as char *ptr. Note that char* a, b declares a as a pointer and b as char. The syntax is that char is a list of "declaration specifiers". These specifiers are followed by a comma-separated list of "declarators". These declarators are *a and b, respectively. If you write char* ptr, you're putting a whitespace division in the middle of the declarator, while clumping it together with the specifier.
This is a bit like taking the arithmetic expression x + x*y + y and writing it as x+x * y+y, so it looks like multiplication is done last.
Avoid such misleading whitespace in programming.