Question

I am confused as to how the following passage matches up with the code that follows it:

Since argv is a pointer to an array of pointers, we can manipulate the pointer rather than index the array. This next variant is based on incrementing argv, which is a pointer to pointer to char, while argc is counted down:

#include <stdio.h>
/* echo command-line arguments; 2nd version */
main(int argc, char *argv[])
{
    while (--argc > 0)
        printf("%s%s", *++argv, (argc > 1) ? " " : "");
    printf("\n");
    return 0;
}

Isn't char *argv[] just an array of pointers? Wouldn't a pointer to an array of pointers be written as char *(*argv[]) or something similar?

As a side note, is it normal that in general I find declarations that mix arrays and pointers rather confusing?

Was it helpful?

Solution

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 ints 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 ints, 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.

OTHER TIPS

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);

Since argv is a pointer to an array of pointers.

This is wrong. argv is an array of pointers.

Since argv is a pointer to an array of pointers,

No, not even close.

Isn't char *argv[] just an array of pointers?

No, it's a pointer to pointers.

"Pointer to the first element of an array" is a common construct. Every string function uses it, including stdio functions that input and output strings. main uses it for argv.

"Pointer to an array" is a rare construct. I can't find any uses of it in the C standard library or POSIX. grepping all the headers I have installed locally (for '([^)]*\*[^)]) *\[') I find exactly 2 legitimate instances of pointer-to-array, one in libjpeg and one in gtk. (Both are struct members, not function parameters, but that's beside the point.)

So if we stick to official language, we have a rare thing with a short name and a similar but much more common thing with a long name. That's the opposite of the way human language naturally wants to work, so there's tension, which gets resolved in all but the most formal situations by using the short name "incorrectly".

The reason we don't just say "pointer to pointer" is that there's another common use of pointers as function parameters, in which the parameter points to a single object that's not a member of an array. For example, in

long strtol(const char *nptr, char **endptr, int base);

endptr is exactly the same type as argv is in main, both are pointer-to-pointer, but they're used in different ways. argv points to the first char * in an array of char *s; inside main you're expected to use it with indexes like argv[0], argv[optind], etc., or step through the array by incrementing it with ++argv.

endptr points to a single char *. Inside strtol, it is not useful to increment endptr or to refer to endptr[n] for any value of n other than zero.

That's semantic difference is expressed by the informal usage of "argv is a pointer to an array". The possible confusion with what "pointer to array" means in formal language is ignored, because the natural instinct to use concise language is stronger than the desire to adhere to a formal definition that tells you not to use the most obvious simple phrase because it's reserved for a situation that will almost never happen.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top