3

So I have read that behind the scenes when passing an array in a function the compiler turns

int myArray(int arr[])

into

int myArray(int *arr)

Also an array most of the times decays to a pointer, for example

arr[0]

is the same as

(arr +  0)

(Correct me if I am wrong)

But when it comes to char *argv it gets confusing, char *argv[] translates to an array of strings.

For example:

argv[2] = "Hello"
argv[3] = "World"

But how is **argv the same as *argv[] since **argv is a pointer to a pointer, how can **argv contain 10 different values since it is a pointer to a pointer? I think I have misunderstood something.

chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • 1
    How can an `int*` contain 10 different values since it's just a pointer to an `int`? – Siguza Jul 13 '22 at 16:30
  • When you call a function handing it an array, what the function receives is a pointer. Somewhere in the code that calls `main`, there is an *array of pointers*, namely `char *argv[]`. So what `main` receives is a pointer to some pointers, or `char **`. – Steve Summit Jul 13 '22 at 16:39
  • 1
    More precisely, what happens is that the "outermost" (or "topmost") array gets turned into a pointer. If you passed an array of structures, the function would receive a pointer to structures. If you passed an array of arrays, the function would receive a pointer to arrays. (But if the array-of-arrays example is confusing, pretend I didn't mention it.) – Steve Summit Jul 13 '22 at 16:41
  • Colloquially for a contiguous array, `int*** == int** == int* == int`. In the end, all pointers are simply a number which stores a memory address. The values of the array in question can be assessed based on the length of the individual arrays and the indices you refer to. A common pseudo-example is for `int**`, where `arr[1][2]` may retrieve the address `arr + (sizeof(int) * len(arr) * 1) + (sizeof(int) * 2)` (this may look different depending on compiler/etc, but as a basic idea of what can happen) – Rogue Jul 13 '22 at 16:47
  • You need to be careful with actual multi-dimensional arrays. Something declared `int array3d[4][4][4];` can **not** be accessed with an `int ***` pointer. In the case of `char **argv` or `char *argv[]`, there's only one level of indirection because `main()` gets passed an array of `char *` pointers, so it works. – Andrew Henle Jul 13 '22 at 17:00
  • "char *argv[] translates to an array of strings." --> not quite. `argv` in `char *argv[]`, in a function parameter, is pointer to a `char *`. It is not necessarily a pointer to a _string_. – chux - Reinstate Monica Jul 13 '22 at 20:16
  • "**argv contain 10 different values since it is a pointer to a pointer? " --> `argv` points tp 1 value, not 10. – chux - Reinstate Monica Jul 13 '22 at 20:19
  • Pointers don't "contain 10 different values". They can point to the first one of 10 different values . Pointers don't contain the things they point to . – M.M Jul 14 '22 at 03:09

5 Answers5

4

Also an array most of the times decays to a pointer for example arr[0] is the same as (arr + 0)

arr[0] is evaluated like *( arr + 0 ) that is the same as *arr.

Function parameters having array types are adjusted by the compiler to pointers to the array element types.

On the other hand, an array used as an argument expression is implicitly converted to pointer to its first element.

So for example these function declarations

void f( char * s[100] );
void f( char * s[10] );
void f( char * s[] );

are equivalent and declare the same one function as

void f( char **s );

To make it clear just introduce a typedef name. For example

typedef char *T;

then you have

void f( T s[] );

So the function parameter is adjusted by the compiler to

void f( T *s );

Now change the typedef alias to its original type and you will get

void f( char * *s );

Pay attention to that the pointer s knows nothing how many elements the array has used as a function argument.

Thus for example the function main is declared like

int main( int argc, char *argv[] );

That is it has one more parameter argc that allows to determine the number of elements in the array of strings passed to the function. Though if to tell about main then in general the parameter argc is redundant because the array of strings always contains the sentinel value NULL. That is argv[argc] is equal to NULL.

But in general you have to pass also the number of elements in the array used as a function argument.

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • "`void f( char * s[100] ); void f( char * s[10] ); void f( char * s[] );` are equivalent" --> Hmm, compilers can do static code analysis and emit warnings like `char *a[30]; f(a);` "warning: 'f' accessing 800 bytes in a region of size 240 [-Wstringop-overflow=]" with `void f( char * s[100] )`, but not with `void f( char * s[10] )`. Not quite equivalent. – chux - Reinstate Monica Jul 13 '22 at 20:02
  • @chux-ReinstateMonica There is no specifier static in the parameter declaration of the function. So any such a warning may be ignored. – Vlad from Moscow Jul 13 '22 at 20:18
1

But how is **argv the same as *argv[] since **argv is a pointer to a pointer

Because, quoting n1570 6.7.6.3p7 (emphasis mine):

A declaration of a parameter as "array of type" shall be adjusted to "qualified pointer to type", where the type qualifiers (if any) are those specified within the [ and ] of the array type derivation. If the keyword static also appears within the [ and ] of the array type derivation, then for each call to the function, the value of the corresponding actual argument shall provide access to the first element of an array with at least as many elements as specified by the size expression.

Each element of char *argv[] has type char *, which is a pointer to a char.

So according to 6.7.6.3p7, an array of char * will be adjusted to a pointer to a char *; i.e. char *argv[] (an array of pointer to chars) will be adjusted to char **argv (pointer to a pointer to a char).

how can **argv contain 10 different values since it is a pointer to a pointer?

Because it doesn't. Just because it is a pointer to a pointer, doesn't make it different from any other pointer (Well, except that their size, representation, and alignment requirements may differ).

The diagram below will probably help you understand what is actually going on:

argv (points to the beginning of the array of pointer to chars.)
------------------+---------+---------+
    \             |         |         |
     \ argv[0]    |argv[1]  |argv[2]  | argv[3]
      \           |         |         |
       \          |         |         |
        V  char * V  char * V char *  V char *
        +---------+---------+---------+---------+
        |  0xf00  |  0xf0C  |  0xf13  |   NULL  |   (0xf0C and 0xf13 are the addresses of the first element of the strings passed as parameters to your program.)
        +---------+---------+---------+---------+
             |           |        |
             |           |        |
             |           |        |
             V           V        V
            "my_        "hello"  "world"            ("hello" and "world" are the parameters passed to your program.)
            program"
0

"**" simply refers to a pointer to a pointer. Array itself works as an pointer. Therefore we can say that int **argv == *argv[] = argv[][0].

0

char *argv[] translates to an array of strings

If you consider this snippet

char const *arr[] = {"one", "two", "three"};

arr is declared as an array of pointers to char and inititialized with an initializer list.

The C Standard (see e.g. the C11 draft at 6.7.6.2 Array declarators1) says (emphasis mine):

1 In addition to optional type qualifiers and the keyword static, the [ and ] may delimit an expression or *. If they delimit an expression (which specifies the size of an array), the expression shall have an integer type. If the expression is a constant expression, it shall have a value greater than zero. The element type shall not be an incomplete or function type.
[...]
4 If the size is not present, the array type is an incomplete type.

Later, at 6.7.9 Initialization2:

8 An initializer specifies the initial value stored in an object.
[...]
22 If an array of unknown size is initialized, its size is determined by the largest indexed element with an explicit initializer. The array type is completed at the end of its initializer list.


The question is about argv, though, the second function parameter of the main function.

As noted in JASLP's answer, we need to look at 6.7.6.3 Function declarators3

6 A parameter type list specifies the types of, and may declare identifiers for, the parameters of the function.
7 A declaration of a parameter as "array of type" shall be adjusted to "qualified pointer to type", where the type qualifiers (if any) are those specified within the [ and ] of the array type derivation. If the keyword static also appears within the [ and ] of the array type derivation, then for each call to the function, the value of the corresponding actual argument shall provide access to the first element of an array with at least as many elements as specified by the size expression.


1) http://port70.net/%7Ensz/c/c11/n1570.html#6.7.6.2

2) http://port70.net/%7Ensz/c/c11/n1570.html#6.7.9

3)http://port70.net/%7Ensz/c/c11/n1570.html#6.7.6.3

Bob__
  • 12,361
  • 3
  • 28
  • 42
-2

Array and Pointers in C Language hold a very strong relationship. Generally, pointers are the variables which contain the addresses of some other variables and with arrays a pointer stores the starting address of the array. Array name itself acts as a pointer to the first element of the array and also if a pointer variable stores the base address of an array then we can manipulate all the array elements using the pointer variable only.

Difference between pointer and array in C: https://www.geeksforgeeks.org/difference-pointer-array-c/

  • 4
    "Array name itself acts as a pointer". That's a popular explanation, but it turns out it's misleading and will eventually lead to trouble. A true statement is that in an expression, when you try to use the value of an array, what you get is a pointer to the array's first element. – Steve Summit Jul 13 '22 at 16:59
  • @SteveSummit I don't even like the word "pointer" there. I prefer "address", as in "a bare/unadorned array in an expression evaluates to the address of the first element". To me, the use of "pointer" implies an l-value that can be assigned to. – Andrew Henle Jul 13 '22 at 17:03
  • 3
    Also, the explanations at that geeksforgeeks link are, I'm sorry, pretty worthless. For better explanations, search on ["array pointer difference" here](https://stackoverflow.com/search?q=array+pointer+difference+%5Bc%5D), or see the [C FAQ list](https://c-faq.com/aryptr/practdiff.html). – Steve Summit Jul 13 '22 at 17:17