Vectors change addresses, is reserve the only solution?

tldr: On the curious side, why do memory addresses of all the elements in a list change whenever I add an element? On the practical side, what advice can be given in cases where vector::reserve is not a prefered option.

Windows 10, win-builds gcc compiler

Hello. First of all, no worries, I am not actually trying to build anything, I am just practicing c++ pointers and having fun looking under the hood. I am aware by now that addresses which I am getting in this program are not very useful, but I'd like to know more about what is going on.

This program is sort of a playground where I dynamically add values at runtime (currently only integers).
Here is the code. 
-If typed in command is type:value (ie "int:1000") it stores value into the vector of that type, currently only int is implemented. As a side thing, it also stores and displays info about values' memory address as strings, and that is the thing I am curious about.
-If the command is "types:index" (ie "ints:0") it shows the current address of that element. It turned out the addresses change.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <iostream>
#include <vector>
#include <sstream>

using namespace std;

// Add int to the ints list and return descriptive string: "( type ) value | address"
string parseInt(string val, vector<int> &ints) {
    int value = stoi(val);
    ints.push_back(value);
    ostringstream oss;
    oss << &ints.back();
    return "( int ) " + val + " | " + oss.str();
}

// Parse command, very primitive, usage:
// "type:value" to create new type and add to type's list
// "types:index" to get address of variable at type's list's index
void parseVariable(string input, vector<string> &variables, vector<int> &ints) {

    string space = ":";
    if(input.find(space) == string::npos) return;
    int spacePos = input.find(space);
    string variableType = input.substr( 0, spacePos );
    string variableValue = input.substr( spacePos+1, input.length() );

    if( variableType == "int" )
        variables.push_back( parseInt(variableValue, ints) );

    if( variableType == "ints") {
        int val = stoi(variableValue);
        //vector<int>* pints = &ints;
        ostringstream oss;
        oss << &ints[val];//(pints+val);
        cout << "Element " << to_string(ints[val]) << " is at " << oss.str() << endl;
    }
}

int main() {

    vector<string> variables; // List of variable descriptions
    vector<int> ints; // List that is supposed to allow int variables to live at constant addresses (but does not)
    string input = "";

    while( input != "exit") {
        cout << endl;
        for(string variable : variables) { // list vars
            cout << variable << endl;
            cout << "----------" << endl;
        }
        cout << endl << ">>"; cin >> input; cout << endl; // input prompt
        parseVariable(input, variables, ints);
    }

    return 0;
}


What program does is best described by showing the console prompt and output.Note: lines starting with >> are prompt, there I enter "command:value"

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

>>int:10


( int ) 10 | 0x6e1b00

>>int:100


( int ) 10 | 0x6e1b00
----------
( int ) 100 | 0x6e1ad4
----------

>>int:1000


( int ) 10 | 0x6e1b00
----------
( int ) 100 | 0x6e1ad4
----------
( int ) 1000 | 0x6e1b58
----------

>>ints:0

Element 10 is at 0x6e1b50

( int ) 10 | 0x6e1b00
----------
( int ) 100 | 0x6e1ad4
----------
( int ) 1000 | 0x6e1b58
----------

>>ints:1

Element 100 is at 0x6e1b54

( int ) 10 | 0x6e1b00
----------
( int ) 100 | 0x6e1ad4
----------
( int ) 1000 | 0x6e1b58
----------

>>exit


Process returned 0 (0x0)   execution time : 186.859 s
Press any key to continue.


So the commands "ints:0" and "ints:1" retrieve the addresses of ints[0] and ints[1], ( "Element 10 is at 0x6e1b50"... ) and it turns out that they are different from what is shown in the string recorded at moment of creation ( ( int ) 10 | 0x6e1b00 and so on... ), and also they keep changing as I add more elements.

To verify that this is really the case, I modified the listing loop in main() to show addresses in "real time" as well, rather than just recorded strings, like so:

1
2
3
4
5
6
7
   int index = 0; //ADDED
        for(string variable : variables) { // list vars
            cout << variable << endl;
            cout << "(" << &ints[index] << ")" << endl; //ADDED
            cout << "----------" << endl;
            index++; //ADDED
        }

 
Which may be not needed, but being a noob I wasn't sure if I was falling into some c++ trap when addresses are called from other functions.

The result shows addresses indeed change every time I alter the list. Addresses in parenthesis are fetched at time the call is made, each is different from its old address shown above it. From what I understand the starting address gets changed quite a bit (which is unexpected), and then each next element shifts by 4 (which is expected I guess).  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

>>int:10


( int ) 10 | 0x701b00
(0x701b00)

>>int:100


( int ) 10 | 0x701b00
(0x701ad0)
----------
( int ) 100 | 0x701ad4
(0x701ad4)
----------

>>int:1000


( int ) 10 | 0x701b00
(0x701b50)
----------
( int ) 100 | 0x701ad4
(0x701b54)
----------
( int ) 1000 | 0x701b58
(0x701b58)
----------

>>test


( int ) 10 | 0x701b00
(0x701b50)
----------
( int ) 100 | 0x701ad4
(0x701b54)
----------
( int ) 1000 | 0x701b58
(0x701b58)
----------

>>exit


Process returned 0 (0x0)   execution time : 34.838 s
Press any key to continue.
   

So, at first I thought this is harmless and I'm just looking at things which are not meant for my eyes. 
But then it occurred to me that if pointers hold memory addresses, and the addresses of vector elements keep changing, then if I ever code something that uses pointers of vector elements, they may in some cases (and I don't even know all the cases atm) become useless and end up pointing at random values.
So, to test it, a command that activates a tracker of single value, used with "trackint:index":

Added global at top: int* trackedInt = NULL;

In main(), below the listing loop:
 
if(trackedInt != NULL)  cout << "Tracked integer's value: " << *trackedInt << endl;


New command in parseVariable function:
1
2
3
4
   if( variableType == "trackint") {
        int val = stoi(variableValue);
        trackedInt = &ints[val];
    }


And the result shows that things do go foobar for integer tracker whenever I add an element.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

>>int:10


( int ) 10 | 0x7a1b00
(0x7a1b00)

>>trackint:0


( int ) 10 | 0x7a1b00
(0x7a1b00)
----------
Tracked integer's value: 10

>>int:100


( int ) 10 | 0x7a1b00
(0x7a1ae0)
----------
( int ) 100 | 0x7a1ae4
(0x7a1ae4)
----------
Tracked integer's value: 8020600

>>int:1000


( int ) 10 | 0x7a1b00
(0x7a1b50)
----------
( int ) 100 | 0x7a1ae4
(0x7a1b54)
----------
( int ) 1000 | 0x7a1b58
(0x7a1b58)
----------
Tracked integer's value: 1875683832

And so on...
 

The value that trackInt is pointing to changes to a large random number as soon as an element is added. This would mean bugs&crashes if I ever relied on using pointers with vectors.

So, I did look this up, and the recommended solution is to use vector::reserve , but the very reason I chose vector over array is so I don't have to worry about pre-defining the limits to the number of elements that can be added. Are there any other solutions or advice one can give?
> but the very reason I chose vector over array is so I don't have to worry about pre-defining the limits to the number of elements that can be added.
> Are there any other solutions or advice one can give?

If random access is not critical, we can use std::list as the sequence container.

Iterators, pointers and references to an item in std::list are not invalidated when other items are added, removed or moved.
Thank you, I see the tradeoff is the std::list is a linked list and is less elegant when having to fetch a value at specific index.

You have also helped me improve another toy program, I was making a hash table according to some no-code article which described it to be an array of linked lists (linked lists to deal with index collision in array), but I wasn't sure what a linked list is so I used vector of vectors, now I have properly implemented it as an array of std::list elements.
Topic archived. No new replies allowed.