0

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!

karacho84
  • 11
  • 2
  • check here - https://stackoverflow.com/questions/27213580/difference-between-char-argv-and-char-argv-for-the-second-argument-to-main – A R Oct 24 '17 at 18:27
  • Remember that for function arguments `[]` is translated as a pointer. So `char *argv[]` is really `char **argv`. Now think a little what `char *(*argv)[]` might be translated to... – Some programmer dude Oct 24 '17 at 18:27
  • Also note that the C specification tells what the types of the arguments to `main` is. Deviate from that and your program is no longer valid. I recommend you read e.g. [this `main` function reference](http://en.cppreference.com/w/c/language/main_function). – Some programmer dude Oct 24 '17 at 18:29
  • @Someprogrammerdude If I'm not mistaken `char* (*argv)[]` should translate to `char*** argv`. What i don't unterstand is why dereferencing argv still works, even if the address of operator was not used when when passing argv to `main`. – karacho84 Oct 24 '17 at 18:40
  • @karacho84 - Your comment differs from your question! Which one is correct? Anyway - `char* (*argv)[]` is just wrong. The warning kind of tell you that. – Support Ukraine Oct 24 '17 at 18:41
  • @karacho84 It's pure luck it works. And that luck is because of how memory and pointers are laid out and handled on modern PC-like systems. In reality it's *undefined behavior*. – Some programmer dude Oct 24 '17 at 18:43
  • @AbhinavRisal thank you, but that is the code i usually write, either `char* argv[]` or `char** argv`, which are, to my understanding, the same thing for the compiler. What my colleague writes is `char* (*argv)[]`, which would be equal to `char*** argv` if I'm not mistaken. (Edit: SO deleted some of the asterisk, deleted and added again with inline code formatting) – karacho84 Oct 24 '17 at 18:44
  • @Someprogrammerdude I would assume _undefined behavior_ miraculously working here, too. Thank you! – karacho84 Oct 24 '17 at 18:47
  • @4386427 corrected the comment, sorry. The formatter removed the `*`s :) – karacho84 Oct 24 '17 at 18:48
  • It is very strange that your sample program prints “first” when the source code contains “frist”. Is there a spell-checker built into your compiler? – Eric Postpischil Oct 24 '17 at 20:56

1 Answers1

1

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.

John Bode
  • 119,563
  • 19
  • 122
  • 198