Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it acceptable to pass a pointer as an argument for a double pointer in C?

So I've been doing some learning projects in c, and I'm starting to get more comfortable with pointers, however I have come across some phenomena that I can't seem to find much information for.

Basically, I make an array, for simplicity's sake I'm starting with an integer array:

int x[2] = {1, 3};

Now, if I want to modify the contents of this array, I found that I can make a function which takes an integer pointer as a parameter, and pass x as an argument, and dereference x by specifying an index.

#include <stdio.h>

void foo(int* input){
    input[0] = 2;
    input[1] = 4;
}

int main(){
    int x[2] = {1, 3};
    printf("x[0] before: %d\nx[1] before: %d\n", x[0], x[1]);
    foo(x);
    printf("x[0] after: %d\nx[1] after: %d\n", x[0], x[1]);
}

output

x[0] before: 1
x[1] before: 3
x[0] after: 2
x[1] after: 4

which, is interesting, but I don't see it done often, so im unsure if it's acceptable.

Now, for the bigger question, for some reason, whenever I want to change the address that the pointer itself points to, by specifying a new value and pointer, and then setting the argument to that new pointer, it only seems to work whenever I do the following:

#include <stdio.h>
#include <stdlib.h>

void foo(int** input){
    int x[3] = {2, 4};
    int** ptr = x;
    *input = *ptr;
}

int main(){

    int x[2];
    x[0] = 1;
    x[1] = 3;
    printf("x[0] before: %d\n", x[0]);
    printf("x[1] before: %d\n", x[1]);
    foo(x);
    printf("x[0] after: %d\n", x[0]);
    printf("x[1] after: %d\n", x[1]);

    return 0;
}

Output:

x[0] before: 1
x[1] before: 3
x[0] after: 2
x[1] after: 4

So I have several questions that I can't seem to find an answer to, probably due to my lack of technical vocabulary: Answers added as edit below

1: Whenever I pass the array with and without referencing as an argument, why does the argument behave as an array of pointers, without initializing an array of pointers and setting it to the address of the array first? Are there any unintended consequences of doing this?

Answer by Eric Postpischil

It is normal and common to give an array as an argument for a parameter that is a pointer. The array is automatically converted to a pointer to its first element.

2: If the argument is a pointer, rather than a pointer to a pointer, is it acceptable to pass a pointer as an argument, to a function that takes a double pointer as a parameter like I did, is there any undefined behavior by doing this I should know of?

Answer by Eric Postpischil

foo is declared to take an int **, so the compiler converted the pointer to the type int . But it still points to x[0]. int ptr = x; initializes ptr to point to the first element of the x local to foo. Again, the compiler converts the pointer to the type int **.

3: Why does this work?

Answer by Eric Postpischil

In your C implementation, int is four bytes, so eight bytes is two int. When the compiler loads eight bytes from memory, it gets the two int that the pointer is pointing to, so it gets the bytes of x[0] and x1 (in the local array x).

EDIT:

Thank you everyone for all of your help, as it turns out, my compiler will not allow that weird solution to work, it just happened to work when running the program in debug mode on visual studio code, when compiled in the terminal, it will indeed throw error codes.

user Lundin posted this helpful link for compiler options for beginners

like image 498
unnecessary_bootstrapping Avatar asked Oct 25 '25 14:10

unnecessary_bootstrapping


2 Answers

… which, is interesting, but I don't see it done often, so im unsure if it's acceptable.

It is normal and common to give an array as an argument for a parameter that is a pointer. The array is automatically converted to a pointer to its first element.

This conversion is performed whenever an array is used in an expression other than as the operand of sizeof, as the operand of unary &, as the operand of a typeof operator, or as a string literal used to initialize an array.

Regarding this code:

void foo(int** input){
    int x[3] = {2, 4};
    int** ptr = x;
    *input = *ptr;
}

This is horrible, and you should never do it.

What is actually happening here is:

  • You gave the array x as an argument. This was automatically converted to a pointer to its first element. That is an int * whose value is &x[0].
  • foo is declared to take an int **, so the compiler converted the pointer to the type int **. But it still points to x[0].
  • int** ptr = x; initializes ptr to point to the first element of the x local to foo. Again, the compiler converts the pointer to the type int **.
  • In *input = *ptr;, ptr is dereferenced. Because its type is int **, the compiler expects it to refer to an int *. In your C implementation, an int * takes eight bytes, so the compiler loads eight bytes from memory.
  • In your C implementation, int is four bytes, so eight bytes is two int. When the compiler loads eight bytes from memory, it gets the two int that the pointer is pointing to, so it gets the bytes of x[0] and x[1] (in the local array x).
  • Then, for the assignment to *input, the compiler expects *input to refer to eight bytes, and it writes the eight bytes it loaded to memory. This is the memory of the x[0] and x[1] in main, so those elements are overwritten with the bytes from the x in foo.

This all “worked” by coincidence. It is in fact behavior that is not defined by the C standard: These implicit pointer conversions are not defined, accessing memory using the wrong type is not defined, and the pointer to x[0] may not be correctly aligned for an int *. If you enable optimization in the compiler or otherwise change the program or how it is executed, the program may cease “working.”

like image 134
Eric Postpischil Avatar answered Oct 27 '25 02:10

Eric Postpischil


There is nothing wrong with your first program. However, your second program is invoking undefined behavior due to violating the strict aliasing rule, because it is accessing an int as if it were an int *.

What is happening in your second program is the following:

The line

int** ptr = x;

is not allowed in standard C, because it converts a int * to an int ** without an explicit type cast. The compiler Clang only gives you a warning, whereas the compiler GCC will outright reject this line. Therefore, I am assuming that you are using the compiler Clang.

The compiler Clang will treat this line as if you had used an explicit type cast:

int** ptr = (int **)x;

In the next line

*input = *ptr;

you are treating the pointer ptr as it it were pointing to an object of type int *, although it is pointing to a simple int. As previously stated, this violates the strict aliasing rule (thereby invoking undefined behavior), but in your case, it appears that you are lucky and that your program works as intended. This is probably due to the fact that you are on a platform on which pointers are 64 bits in size, which happens to be the same size as the entire array.

For the CPU, copying a 64-bit pointer is the same thing as copying an array of two 32-bit values, because the total size of the data is 64 bits in both cases. However, from the perspective of the compiler, it is not the same thing, because you are breaking the rules of the C language, and such things can easily go wrong if the compiler decides to rearrange your code for optimization purposes.

In C, the correct way to perform such a copy of the array is to pass an int * (as you are doing in your first posted program) instead of a int ** (as you are doing in your second posted program) to the function foo and to copy the individual elements of the array:

void foo( int *input )
{
    int x[2] = { 2, 4 };

    // In the following, "sizeof x" is the size of the
    // array in bytes, and "sizeof x[0]" is the size of 
    // a single element, so "sizeof x / sizeof x[0]" is 
    // the number of elements in the array, which is 2.

    for ( int i = 0; i < sizeof x / sizeof x[0]; i++ )
    {
        input[i] = x[i];
    }
}

Alternatively, you can use the function memcpy to copy the individual bytes of the array:

void foo( int *input )
{
    int x[2] = { 2, 4 };

    memcpy( input, x, sizeof x );
}

If you instead want the function foo to change the variable x in the function main so that it refers to a different array, then you should make the variable x a pointer instead of an array, so that it has the ability to point to a different array. If you then pass a pointer to the pointer x to the function foo, the function foo can use that pointer to change the value of the pointer x, so that it points to a different array. Here is an example:

#include <stdio.h>

void foo( int **input )
{
    // This array must have static storage duration, otherwise
    // its lifetime will end as soon as the function terminates.
    static int second_array[2] = { 2, 4 };

    *input = second_array;
}

int main( void )
{
    int first_array[2] = { 1, 3 };
    int *x = first_array;
    
    printf("x[0] before: %d\nx[1] before: %d\n", x[0], x[1]);
    foo( &x );
    printf("x[0] after: %d\nx[1] after: %d\n", x[0], x[1]);
}

This program has the following output:

x[0] before: 1
x[1] before: 3
x[0] after: 2
x[1] after: 4
like image 40
Andreas Wenzel Avatar answered Oct 27 '25 03:10

Andreas Wenzel



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!