3

recently I've encountered very very peculiar question when using auto in C++, just ... just look at the following code snippet :

my main function:

#include <list>
#include <iostream>
#include <stdio.h>
int main(){ 
    int a = 10, b = 20, c = 30;
    list<int> what;
    what.push_back(a);
    what.push_back(b);
    what.push_back(c);

    read(what);

    return 0;
}

And here's function read:

void read(const list<int>& con){

    for (auto it : con){
        printf("%p\n", &it);
        cout << it << endl;
    }
    return ;
}

And here's is the output :

0x7fffefff66a4
10
0x7fffefff66a4
20
0x7fffefff66a4
30

What the heck is that? Same address with different content !?

And more strange this is, if I modify the for-loop by adding an '&'
that is:

for (auto& it : con){

All the output makes sense immediately, the addresses would change by iteration

So my question is,
Why does the '&' sign make a change under this circumstance?

UKMonkey
  • 6,941
  • 3
  • 21
  • 30
Shih-Chan Huang
  • 219
  • 2
  • 9
  • You have too many questions in your post. Which one would like answered here? – François Andrieux Dec 05 '17 at 14:29
  • @FrançoisAndrieux : Why does the '&' sign make a change under this circumstance? – Shih-Chan Huang Dec 05 '17 at 14:30
  • 2
    And you should definitely [get a good beginners book or two](http://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list) if you don't understand the meaning of `&` in its different forms. – Some programmer dude Dec 05 '17 at 14:31
  • `auto it` it makes a temporary copy, so address is the same on each iteration, what do you not understand? – Killzone Kid Dec 05 '17 at 14:32
  • 6
    Your question isn't about `auto`, it's about iterators. Replace `auto` with `int`, same results. – Rotem Dec 05 '17 at 14:32
  • @KillzoneKid Since it makes a temp copy, then where do these same addresses point to? – Shih-Chan Huang Dec 05 '17 at 14:36
  • **Every** object has an address. It is showing the address of the temporary copy – Caleth Dec 05 '17 at 14:36
  • 2
    The compiler is free to reuse memory for variables inside loops. It would not make sense to allocate new memory for a loop of a few million iterations, for example. – Some programmer dude Dec 05 '17 at 14:38
  • 1
    @Shih-ChanHuang in case of copy, the same memory is reused every iteration, hence the same address. In case of reference, the address is the address of the actual memory containing list element. so it is different for every element – Killzone Kid Dec 05 '17 at 14:38

3 Answers3

20
for (auto it : con){

Same address with different content !?

This is very typical for variables with automatic storage duration. This has nothing to do with auto in C++. You would get the same result if you had used int:

for (int it : con){

The lifetime of it (as well as each automatic variable within the loop) is just a single iteration. Since the lifetime of the it in last iteration was ended, the next iteration can re-use the same memory, and that's why the address is the same.


Why does the '&' sign make a change under this circumstance?

Because T& declares a reference to type T. Reference variables are different from non-references (object variables). Instead of holding a value such as an object would, a reference instead "refers" to another object.

When you use the addressof operator on a reference, the result will be the address of the referred object; not the address of the reference (which might not even have an address, since it's not an object). That is why the address changes in the latter case. In this case, the references would refer to the int objects that are stored in the nodes of what (because con itself is a reference, and refers to the passed object).


† I mention in C++, because in C auto is in fact a storage class modifier that signifies automatic storage class. It has never had that meaning in standard C++, and its use obsolete in C as well. It's a vestigial keyword from the B language.

In C++, auto declares a type that will be deduced from context.

eerorika
  • 232,697
  • 12
  • 197
  • 326
7

let's see the expanded version of the : loop syntax first.

for( auto it: container) {
    ...
}

is conceptually the same as

for( auto _it = container.begin(); _it != container.end(); it++) {
    auto it = *_it;
    ...
}

while the reference form:

for( auto& it: container)

is the same as

for( auto _it = container.begin(); _it != container.end(); it++) {
    auto &it = *_it;
    ...
}

So in the first case it is a copy of the items in the container, in the second case it is a (lvalue) reference of it, hence if you modify it in the second loop it affects the items in the container

The address issue too can be explained this way: in the copy example the local variable has always the same address in each loop iteration (because their lifetime do not overlap, the compiler has no reason not to use the same address in the stack), thought if you factorize the code inside a function you may observe it changing in different function invocation (because the stack size might be different), in the reference example the address is different every time, because taking the address of a reference will yield the address of the object being referenced (in this case, the item in the container)

pqnet
  • 6,070
  • 1
  • 30
  • 51
  • _"the local variable has always the same address (an address in the stack reserved for that variable)"_ But there isn't just one local variable, there is one for each iteration of the loop. The other answers explain that the compiler is allowed (but not required) to reuse the same memory location for each of those variables, because their lifetime ends before the next iteration starts. – Jonathan Wakely Dec 05 '17 at 15:29
  • @JonathanWakely my answer justifies the _observed_ behavior of the variable address being the same, in term of what the compiler might have done. Whether relying on the address being the same is wrong from a standard point of view, while may be an interesting discussion, is out of the scope of the question being asked. Indeed the variables are local to the iteration thus there are many local variables, the sentence might need a clarification on this aspect. – pqnet Dec 05 '17 at 15:43
  • Your answer simply states the observed behaviour, it doesn't really explain it. The other answers explain it clearly, and so I'm saying you could improve this answer by explaining the observation, not just observing it :-) (Which I see you've now done, so +1) – Jonathan Wakely Dec 05 '17 at 15:45
  • @JonathanWakely it does in my opinion. My interpretation of the question is "how can the value be different and the address be the same", which is explained by copy semantic & compiler policy on stack allocation, while in the reference case both the value and the address of the variable are required to be different (by the c++ semantic, since they are reference to different objects whose lifetime exceed the loop iteration) – pqnet Dec 05 '17 at 15:54
  • for a literal explanation of "why the address is the same", the only correct explanation would be "All the local variables have the same address because the compiler decided so" (since he's allowed to do anything). Comparing the address of two objects whose lifetime do not overlap has no standard defined behavior (if the lifetime overlap they are required to be different), hence the only explanation of this observation can be a guess on how the compiler behaved and why. – pqnet Dec 05 '17 at 15:57
6

Note that auto is standing in for int in your case. So it's a red herring. Consider

for (int i = 0; i < 10; ++i){
    int j = i;
    cout << (void*)&j << '\n';
}

Since j has automatic storage duration, it is most likely created each time with the same address - but points to a different value - , j is being pushed then popped from a stack on each iteration (let's set aside compiler optimisations). That is what is happening in your case with for (auto it : con){. it has automatic storage duration.

When you write

for (auto& it : con){

it is a reference to an int within the container con, so its address will differ on each iteration.

Bathsheba
  • 231,907
  • 34
  • 361
  • 483