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!
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.