argv pointer to an array of pointers
Such terms as "pointer to array" or "to point to an array" are often treated rather loosely in C terminology. They can mean at least two different things.
In the most strict and pedantic sense of the term, a "pointer to array" has to be declared with "pointer to array" type, as in
int a[10];
int (*p)[10] = &a;
In the above example p
is declared as a pointer to array of 10 int
s and it is actually initialized to point to such an array.
However, the term is also often used is its less formal meaning. In this example
int a[10];
int *p = &a;
p
is declared as a mere pointer to int
. It is initialized to point to the first element of array a
. You can often hear and see people say that p
in this case also "points to an array" of int
s, even though this situation is semantically different from previous one. "Points to an array" in this case means "provides access to elements of an array through pointer arithmetic", as in p[5]
or *(p + 3)
.
This is exactly what is meant by the phrase "...argv
is a pointer to an array of pointers..." you quoted. argv
's declaration in parameter list of main
is equivalent to char **argv
, meaning that argv
is actually a pointer to a char *
pointer. But since it physically points to the first element of some array of char *
pointers (maintained by the calling code), it is correct to say semi-informally that argv
points to an array of pointers.
That's exactly what is meant by the text you quoted.
Where C functions claim to accept arrays, strictly they accept pointers instead. The language does not distinguish between void fn(int *foo) {}
and void fn(int foo[])
. It doesn't even care if you have void fn(int foo[100])
and then pass that an array of int [10]
.
int main(int argc, char *argv[])
is the same as
int main(int argc, char **argv)
Consequently, argv
points to the first element of an array of char
pointers, but it is not itself an array type and it does not (formally) point to a whole array. But we know that array is there, and we can index into it to get the other elements.
In more complex cases, like accepting multi-dimensional arrays, it is only the first []
which drops back to a pointer (and which can be left unsized). The others remain as part of the type that is being pointed to, and they have an influence on pointer arithmetic.
The array-pointer equivalence thing only holds true only for function arguments, so while void fn(const char* argv[])
and void fn(const char** argv)
are equivalent, it doesn't hold true when it comes to the variables you might want to pass TO the function.
Consider
void fn(const char** argv)
{
...
}
int main(int argc, const char* argv[])
{
fn(argv); // acceptable.
const char* meats[] = { "Chicken", "Cow", "Pizza" };
// "meats" is an array of const char* pointers, just like argv, so
fn(meats); // acceptable.
const char** meatPtr = meats;
fn(meatPtr); // because the previous call actually cast to this,.
// an array of character arrays.
const char vegetables[][10] = { "Avocado", "Pork", "Pepperoni" };
fn(vegetables); // does not compile.
return 0;
}
"vegetables" is not a pointer to a pointer, it points directly to the first character in a 3*10 contiguous character sequence. Replace fn(vegetables) in the above to get
int main(int argc, const char* argv[])
{
// an array of character arrays.
const char vegetables[][10] = { "Avocado", "Pork", "Pepperoni" };
printf("*vegetables = %c\n", *(const char*)vegetables);
return 0;
}
and the output is "A": vegetables itself is pointing directly - without indirection - to the characters, and not intermediate pointers.
The vegetables assignment is basically a shortcut for this:
const char* __vegetablesPtr = "Avocado\0\0\0Pork\0\0\0\0\0\0Pepperoni\0";
vegetables = __vegetablesPtr;
and
const char* roni = vegetables[2];
translates to
const char* roni = (&vegetables[0]) + (sizeof(*vegetables[0]) * /*dimension=*/10 * /*index=*/2);