In the correct context (namely, arguments to a function), then the following declarations are equivalent:
int main(int argc, char *argv[]);
int main(int argc, char **argv);
int main(int argc, char *argv[12]); // Very aconventional!
Similar comments apply to the function definitions (which have a block enclosed in braces in place of the semi-colon).
In any other context, there are important differences between the notations. For example:
extern char *list1[];
extern char **list2;
extern char *list3[12];
The first says that somewhere there is an array of indeterminate size containing 'char *' values. The second says that somewhere - possibly here - there is a single value containing a pointer to a char pointer. The third says that somewhere - possibly here - there is an array of 12 character pointers.
However, all the three lists can be referenced in somewhat the same way - assuming that they actually have been defined and initialized.
list1[0][0] = '1';
list2[0][0] = '2';
list3[0][0] = '3';
Further, if they are passed into a function like this:
function(list1, list2, list3);
then the function can be declared as:
void function(char **list1, char **list2, char **list3);
The arrays (list1, list3) decay from the array to the pointer to the first element of the array; list2, of course, is already a pointer to a pointer.
One detail to note in a function such as:
void otherfunction(char *list[12])
{
...
}
The C compiler does not treat that declaration any differently from:
void otherfunction(char **list)
{
...
}
or
void otherfunction(char *list[])
{
...
}
In particular, it does no array bounds checking, and as far as the function is concerned, the 12 may as well be absent.
C99 introduces VLA (variable length array) types and also introduces a notation with 'static' and a size in the array bounds. You would need to read the standard to understand those fully.
Suffice to say in a function like the following the size of the array does matter, and is determined at run-time. With two-dimensional arrays in general, all the dimensions except the first need to be specified.
void vla_function(size_t m, int vla[m][m]);
Quoting from the standard (section 6.7.5.3):
void f(double (* restrict a)[5]);
void f(double a[restrict][5]);
void f(double a[restrict 3][5]);
void f(double a[restrict static 3][5]);
(Note that the last declaration also specifies that the argument corresponding to a in any call to f must be a
non-null pointer to the first of at least three arrays of 5 doubles, which the others do not.)