Search code examples
carrayspointersprogram-entry-point

Pointer to array of pointer to char vs. pointer to pointer to char (or char** argv vs. char* (*argv)[])


I had a discussion with a colleague today regarding his (for me) unusual 'main' function signature. He likes to declare it like so:

int main(int argc, char* (*argv)[]) {
   printf("at index 0: %s\n", (*argv)[0]);
}

while I usually write the following code:

int main(int argc, char** argv)
{
    printf("at index 0: %s\n", argv[0]);
}

Sometimes i write "char* argv[]", to make it more clear that argv is an array of pointers to chars.

The code in the first example does compile with a warning

warning: second argument of ‘main’ should be ‘char **’

but it works. My question is WHY the code works, as I had expected a crash due to dereferencing argv. Does the compiler know that the function signature is different, and allows (*argv) as a kind of "no-operation" here?

Trying to understand what's going on, I wrote the following example, which to my surprise does not crash either and prints "first" (but the compiler emits a warning):

#include <stdio.h>

int my_function(int argc, char* (*argv)[])
{
    printf("at index 0: %s\n", (*argv)[0]);
}

int main(void)
{
    char* stringArr[] = { "frist",
                          NULL };
    size_t stringArrSz = sizeof(stringArr)/sizeof(*stringArr);

    return my_function(stringArrSz, stringArr);
}

I had expected that I would need to pass "&stringArr" (use the address operator) to make the code run. That - of course - works too, and fixes the compiler warning.

Please let my know if my question is unclear, any help is greatly appreciated!


Solution

  • My question is WHY the code works, as I had expected a crash due to dereferencing argv. Does the compiler know that the function signature is different, and allows (*argv) as a kind of "no-operation" here?

    "Appearing to work as intended" is one of the possible outcomes of undefined behavior.

    A valid pointer value is still being passed for argv from the runtime environment, so I would not expect the code to crash just from accessing argv[0], regardless of how it was declared.

    Where things will get interesting is when you try to access argv[1], argv[2], etc. While the C language itself doesn't guarantee that pointers to different object types have the same size, in practice they do on most modern architectures like x86; IOW, sizeof (T *) == sizeof (T **) == sizeof ( T *** ). Thus, p + 1 should yield the same byte offset for each of those types on that platform.

    Your colleague is flirting with disaster, though. Types do matter, and if you were working on a system where different pointer types had different sizes, declaring argv as char *(*)[] instead of char *[] or char ** could have some unexpected consequences.