Home > Software engineering >  What will happen when passing an array name to a function?
What will happen when passing an array name to a function?

Time:11-04

I am trying to figure out array and pointer relations in C and wrote a program as below:

#include <stdio.h>
void printd(char *v[]);

int main() 
{
 char *lineptr[] = {"hello", "c", "world"};
 printd(lineptr);
 return 0;
}

void printd(char *lineptr[])
{
    
    printf("%s\n", *lineptr  );
    printf("%s\n", lineptr[0]);
}

After run this program, I got "hello" printed first and then "c". By the defintion in main(), lineptr is an array of pointers.

After passing this array name to the function, I take this array name as a pointer and operate this pointer with *lineptr to dereference it in the first printf. In the second printf, I use the lineptr[0], I got the string "c".

Intuitively, I think this should be an array and lineptr[0] will give us the first element (pointer) in this array. But the result is the second pointer. It seems my understanding is wrong somehow.

Based on the result, as a pointer, lineptr was increased by lineptr in the first printf, it will move to the second element. In the second printf, lineptr[0] is actually equal to *(lineptr 0). So it will print out "c" but not "hello". Am I understand it correctly this way? Why can we not take lineptr as an array anymore as the definition?

CodePudding user response:

You write:

... by the defintion in main(), lineptr is an array of pointer, after passed this array name to function, I take this array name as a pointer and <......> as a pointer, lineptr was increased by lineptr in the first printf, it will move to the second element, and in the second printf, lineptr[0] is actually equal to *(lineptr 0), so it will print out "c" but not "hello", is that right if I understand it in this way?

Yes. We can always discuss the exact wording but... Yes, it seems you got it pretty correct.

Arrays in C have a kind of special feature. In most cases when you use the identifier for an array (i.e. name of array), it will be converted to a pointer to the first element of the array. An example where this doesn't apply is sizeof but the rule applies in most cases. It happens implicit, you can't see it from the code, you just need to know that that's how arrays are handled in C.

For instance:

int arr[7];
arr[1] = 5;

can be seen as:

int arr[7];
int* p_arr = &arr[0];  // Create pointer to first element of array
*(p_arr   1) = 5;      // access array element using that pointer 
                       //    BTW: it is the same as p_arr[1] = 5

The end result is the same. But for the first code block you don't "see" that "pointer to first element of array", it has no name, it all happens behind the scene.

When you pass an array to a function that "pointer to first element of array" becomes visible because it's simply the name of a function parameter.

Example:

void foo(int* p)  // Same as void foo(int p[])
{
    // Now p is a pointer to the first element of
    // the array used as argument when calling foo
    ....
}

int arr[7];
foo(arr);

When entering foo the pointer p will get the value equal to a pointer to the first element of the array. Again we can make that more "visible" by rewriting the code to:

void foo(int* p)
{
    ....
}

int arr[7];
int* p_arr = &arr[0];
foo(p_arr);

Now the code explicit show that we really pass a pointer to the first element of the array to the function. The prior code example do the same, it's just done implicit. You can't see it as it is done behind the scene.

Further, you ask:

why we cannot take lineptr as an array anymore as the definition?

The answer is the same... due to the implicit conversion of array name/identifier to a pointer to first element. It's just how the inventors of C decided it to be...

I can't tell you for sure why they decided to do so but a guess could be performance. If passing an array to a function would mean "pass every array element" there would be a lot of data copying going on and that would hurt performance.

One could argue that the C syntax could/should allow both ways but in my experience it's seldom (very seldom) that you really need to pass an array by passing the value of all elements so I don't see a need for such language feature.

BTW: Should you for some reason really need to pass a whole array (i.e. all elements), you can put the array into a struct and then pass the struct. But be careful... large arrays will hurt performance and - even worse - may lead to stack overflows

CodePudding user response:

Array indexing is relative. When you use it on a pointer pointing somewhere in the array, the index is calculated from the index where that pointer is pointing at - which may not necessarily be the first item in the array.

In case of *lineptr , operator precedence applies before *. Meaning that you change the local variable lineptr to point 1 item ahead in the array. But as the postfix works, that change only happens after the end of the expression, meaning that *lineptr gives the item before gets applied.

It's also important to know the rule of "array decay" - whenever we use an array inside an expression or pass it to a function as parameter, it "decays" into a pointer to its first element. The first element in thisarray of pointers is a char*. So the char *lineptr[] parameter decays into a pointer to a char*, making it equivalent to char** when used in this function.

Just as an example, an alternative way of implementing this would be to use a loop. We could add a placeholder to mark the end of the array in the form of a null pointer: {"hello", "c", "world", NULL};. This is sometimes called a sentinel, as it guards from accessing out of bounds.

Full example:

#include <stdio.h>

// use const correctness since we shouldn't modify the contents
// also always name the prototype parameter the same as in the function definition
void printd (const char *lineptr[]); 

int main (void) 
{
  const char *lineptr[] = {"hello", "c", "world", NULL}; // sentinel value at the end
  printd(lineptr);

  /* Note that the original lineptr in main() remains intact here, the function
     only applied    to a local copy. */
}

void printd (const char *lineptr[])
{
  while(*lineptr != NULL)
  {
    printf("%s\n", lineptr[0]); // this is the same as *lineptr
    lineptr  ; // change where the local variable lineptr points at
  }
}

CodePudding user response:

The expression *lineptr returns the first pointer (to "hello"), then advance to the 2nd pointer ("c). Perhaps it is easier to see this refactoring:

void printd(char *lineptr[]) {
    printf("%s\n", *lineptr);
    lineptr  ;
    printf("%s\n", *lineptr);
}

I would write it like this:

void printd(char *lineptr[]) {
    printf("%s\n", lineptr[0]);
    printf("%s\n", lineptr[1]);
}

CodePudding user response:

Output is hello then c because of *lineptr and lineptr[0] are not same then the control move in second element of array

  •  Tags:  
  • c
  • Related