0

This has been bothering me for over a week now. It's possible I missed the post that explains this, but I have read a bunch of posts/articles and they either don't tell me anything I don't know or are over my head.

Consider this:

#include<iostream>
using namespace std;

void poo(int *currArray){
    cout << "*currArray: " << currArray << endl;
    int * blah = new int [5];
    cout << "blah: " << blah << endl;

    currArray = blah;
    cout << "currArray after switch " << currArray << endl;
}

int main(){
    int * foo = new int [5];

    cout << "foo: " << foo << endl;

    poo(foo);

    cout << "foo after poo " << foo << endl;
}

which yields the output:

foo: 0x7ffdd5818f20
*currArray: 0x7ffdd5818f20
blah: 0x7ffdd5818ef0
currArray after switch 0x7ffdd5818ef0
foo after poo 0x7ffdd5818f20

As you can see, while the address switching happens inside the function, it doesn't carry over to the main function. I'm also aware this is leaking memory, but I don't think it's relevant to the question.

My understanding is that arrays essentially point to the address of its first element. It is also my understanding that arrays are passed 'by reference'. I.e., the address of the array is passed.

So why is it that the address switching does not persist after poo? Have I not changed the pointer of currArray, which is an alias for foo, to be the pointer for blah?

Dillon Chan
  • 145
  • 1
  • 2
  • 9
  • Well on a side note if what you were intending had worked you would have a pointer to a local object that would be out of scope by the time you attempt to `cout` it, so that is not good. Generally don't make parameters or return values point to local objects whose lifetime is restricted to the local scope. – Carl Jun 09 '18 at 20:52
  • Oh, I see what you're saying. But even if I allocate `foo` and `blah` on the heap using `new`, I still have the same problem. Should I edit my question to include that? – Dillon Chan Jun 09 '18 at 20:58
  • `new` and `delete` should generally be avoided in modern C++, but yes, that would be a good thing to include. I'm posting an answer soon. – Carl Jun 09 '18 at 20:59
  • 1
    arrays may `feel` like pass-by-reference but the pointers are not. Pointers are an integral type. Meaning that it is just an integer holding the address. When you call the method, a copy of that integer (the pointer) is passed to the function. In C and also in C++ arrays are always passed as pointers. – zahir Jun 09 '18 at 21:17
  • 1
    @DillonChan You are passing a copy of your `currArray`s pointer to the function, when you do this `void poo(int *currArray)`(Remember, pass by value). In order to do any thing change inside the `currArray`, you need either a reference `void poo(int* &currArray)` or the address of the pointer `void poo(int** currArray)`. – JeJo Jun 09 '18 at 21:18

4 Answers4

2

What is a pointer? It is a variable that contains memory address. It can be copied, it can be referenced, you could even take its address. So when you declare your function as void poo(int *currArray) you are saying, copy the value of the passed argument into the variable currArray. So currArray now contains the same address as foo. However when you change the value of currArray you only change the value of currArray as it is the copy and not a reference.

You could see Carl's answer on how to pass pointer by reference. One other way you could modify foo is if you pass foo's address to the function. Since now you know where foo is in memory you can change its value by dereferencing it and assigning the address of blah to it. Of course you should free memory occupied by foo (via currArray) first, because if you don't you won't be able to do this after and will develop a memory leak.

Here is the example code:

#include<iostream>

void poo(int **currArray)
{
    std::cout << "currArray: " << *currArray << std::endl;
    int *blah = new int[5];
    std::cout << "blah: " << blah << std::endl;
    delete[] *currArray; // clean after ourselves
    *currArray = blah;
    std::cout << "currArray after switch " << *currArray << std::endl;
}

int main()
{
    int *foo = new int[5];
    std::cout << "foo: " << foo << std::endl;
    poo(&foo);
    std::cout << "foo after poo " << foo << std::endl;
    delete[] foo; // clean after ourselves
}

Demo: https://ideone.com/4UWYlG

foo: 0x560b3e9ffc20
currArray: 0x560b3e9ffc20
blah: 0x560b3ea00c50
currArray after switch 0x560b3ea00c50
foo after poo 0x560b3ea00c50
Killzone Kid
  • 6,171
  • 3
  • 17
  • 37
1

What is important to note here is that since pointers are objects, you are taking a pointer by value when passing foo to poo. So the assignment within the function is actually only applied to the local parameter currArray. In order to make it persist with the pointer outside of the scope you must declare your function like: void poo(int *& currArray); although that would not work on an array. Meaning it's a reference to a pointer to an int. Otherwise the pointer is passed by value.

That being said, what you're trying to do, assign a pointer to a local stack object inside the function poo is not very good since once the function returns your other pointer will be pointing to something that is destructed, which will lead to undefined behaviour.

You should consider reading more on pointers, references and argument passing in any good C++ book.

While you are correct that the address of the array is passed, the pointer itself is passed by value, so the local pointer inside the function points to the same address, but it's a different pointer than the one in main.

Also, you should strongly consider working with std::vector instead of raw arrays since this will be way easier to work with, maintain and not have much of a performance difference, if at all.

Here is a working piece of code:

void poo(int *& currArray){
    /* ... */
}

int main(){
    int foo[5];
    int* doo = foo;

    cout << "foo: " << doo << endl;

    poo(doo);

    cout << "foo after poo " << doo << endl;
}

The reason you must make doo, is that you can not bind a non-const lvalue to the array foo itself. And if you made it const you couldn't assign within that function. If you heap allocated that array it would be fine though since the type of foo would be int*.

Carl
  • 2,057
  • 1
  • 15
  • 20
  • You have missed the second option: `void poo(int** currArray)` See this: https://www.ideone.com/bJvBhi. – JeJo Jun 09 '18 at 21:23
  • Thanks for your answer Carl. Can you elaborate on the error where you can't bind a non-const lvalue to foo? Where is this binding happening? At first, I thought that it was because `foo` is not of type `int *&`, so a temporary pointer was made and attempted to be assigned to `currArray`, which is apparently a no-no. But `doo` also does not have the right type, and why does it work with heap allocated variables? – Dillon Chan Jun 10 '18 at 02:45
  • It works with heap since the type of a heap allocated object is `int*`, which is what the function takes. `int foo[5]` is on the stack and it's type is not `int*`, but `int[5]`. So if your function took a pointer to an array of length 5 then it would be ok. This is why we make `doo` with type `int*` to allow the passing of the parameter. – Carl Jun 10 '18 at 10:57
1

There is some confusion out there regarding passing by value and by reference. This is especially true for arrays.

I won't bother you with the whole picture here, but I will try to address your missunderstanding as consise as possible:

  1. In C++ all parameters are passed by value unless the type contains the reference qualifier & or && e.g. void foo(int& a)
  2. In your example there is no & thus nothing is passed by reference
  3. What I think this confusion regarding passing arrays by reference comes from: you use a pointer, pointing to the beginning of an array, to "pass" the array to a function. Important: you are not passing the array itself but a pointer to its beginning. However, inside of the function you can access the array's elements by the * and [] operators. They will give you access to the actual array elements, not a copy of them. This somehow feels like passing the array by reference. Hence the confusion.

So you can modify the array elements inside of the function and you will see the effects in main. But if you modify the pointer to the array, you won't see any effect in main because that pointer was passed by value.

sebrockm
  • 5,733
  • 2
  • 16
  • 39
0

currArray is not an alias, it is a function-local object whose value has been initialized to that passed, via the argument-passing mechanism, and what happens in a function stays in that function. You could declare it a reference to a pointer and then use it to modify any actual pointer passed (but not an array) but again, that's how references work, not some kind of argument magic.

bipll
  • 11,747
  • 1
  • 18
  • 32