Basically, char * argv [] means an array of char pointers, whereas char ** argv means a pointer to a char pointer.
In any array, the name of the array is a pointer to the first element of the array, that is, it contains the address of the first element.
So, in the code below, in a char array, x, x is a pointer to the first element, '1', which is a character. Therefore, he points to a symbol.
And in the arr array, arr is the first element of the pointer x, which in itself is a pointer to a character. So this is a pointer to another pointer.
Therefore, x is char *, and arr is char **.
When you receive something in a function, the basic rule is that you must specify the type of thing you receive. So either you just say you want to get char **, or you can also say char * arr [].
In the first case, we do not need to think about anything complicated. We just know that we get a char * array. Don't we know that? So we get it and use it.
In the second case, it is simple, as I explained above, that arr is char **, you can put this as its type and get it safely. Now the system knows the type of material that we received, we can access the following elements simply by using the annotation of the array. For example, we got the starting address of the array, we can, of course, move on to the next elements, and, as we know, this is a type, we know what it contains, and how we can use it in the future. We know that it contains a pointer to char, so we can legitimately access them.
void func1(char* arr[]) { //function body } void func2(char** arr) { //function body } int main() { //x, y and z are pointer to char char x[3]={'1', '2', '3'}; char y[3]={'4', '5', '6'}; char z[3]={'7', '8', '9'}; //arr is pointer to char pointer char* arr[3]={x, y, z}; func1(arr); func2(arr); }