1

I started to write a function which had a pointer to a vector as a parameter so that it could modify that vector to output results (as the actual return value was an error code), when I started to think about the memory behind that.

Edit: Adding a new example here to make this clearer - I think I mostly have the idea from the answers already posted, but my example was poor in that it was not a good example of why to use a pointer - and did not exactly exhibit the properties I wanted.

(New example)

ErrorEnumType MyClass::MyFunction((other input parameters), std::vector<StructType> *output_vect)
{
    // (do some processing)
    output_vect->clear();
    bool process_complete = false;
    while(false == error_condition)
    {
        StructType new_element;
        // (do some more processing)
        output_vect->push_back(process_complete);
       // (do some more processing)
    }
    return No_Error;
}

I am aware that I could use a reference instead of a pointer to output_vect, but the linter I was using got mad at me for this, so I'm evaluating whether a pointer makes more sense.

(Old example) For example, if I have the following code:

std::vector<int> *vect = new std::vector<int>();

for (uint32_t i = 0; i < 10; i++)
{
    std::cout << "Ptr: " << vect << " Size " << vect->size() <<  " Max Size " << vect->capacity();
    vect->push_back(i);
    std::cout << " elements 0: " << (*vect)[0] << ", " << i << " :" << (*vect)[i] << std::endl; 
}

And I run it, I get the following output:

Ptr: 0x557c393f9e70 Size 0 Max Size 0 elements 0: 0, 0 :0
Ptr: 0x557c393f9e70 Size 1 Max Size 1 elements 0: 0, 1 :1
Ptr: 0x557c393f9e70 Size 2 Max Size 2 elements 0: 0, 2 :2
Ptr: 0x557c393f9e70 Size 3 Max Size 4 elements 0: 0, 3 :3
Ptr: 0x557c393f9e70 Size 4 Max Size 4 elements 0: 0, 4 :4
Ptr: 0x557c393f9e70 Size 5 Max Size 8 elements 0: 0, 5 :5
Ptr: 0x557c393f9e70 Size 6 Max Size 8 elements 0: 0, 6 :6
Ptr: 0x557c393f9e70 Size 7 Max Size 8 elements 0: 0, 7 :7
Ptr: 0x557c393f9e70 Size 8 Max Size 8 elements 0: 0, 8 :8
Ptr: 0x557c393f9e70 Size 9 Max Size 16 elements 0: 0, 9 :9

It seems as though this could cause major memory issues - because if the vector needs to expand, it could be writing into space which is already being utilized, because it looks like that pointer does not change. Even running this over a much larger loop, this pointer looks like it is constant.

I'm still a (relatively) new programmer, and am not sure that I have the grasp on memory allocation that I would like to. Is my understanding correct - will this cause buffer errors and overwrite adjacent memory? Or is there some protection in std::vector that I am not considering?

(Edit): From some of the answers below, it appears that a layer of indirection between the pointer to the vector and where the elements of the vector are stored were what I was not accounting for. However, since the answers seemed to focus on how I allocated the vector, when I don't plan to use the new operator at all (just call the function like my_obj.MyFunction((other input parameters), &output_vect)), I wanted to make this more clear.

3
  • 1
    I recommend to learn this in two steps: first learn how the memory allocation of a std::vector works internally. Second, you need to learn the difference between new for a single object and new for arrays .
    – Doc Brown
    Apr 10, 2021 at 5:17
  • 2
    ... Finally you need to understand that you mixed up the latter with what vector does internally - allocating a single vector object is what your code does, and it has absolutely nothing to do with the memory allocation which happens inside the vector object.
    – Doc Brown
    Apr 10, 2021 at 5:21
  • 1
    This works because you're pointing to the vector object itself; if instead you had a direct pointer to one of the elements, after relocation they could end up pointing to gibberish. Apr 10, 2021 at 12:31

4 Answers 4

6

No problems luckily. This code is safe.

When calling “new”, a range of bytes is reserved on the heap memory. The size of that range depends on the object type and is known at compile time. That size can not grow or shrink, until you call delete, and you can count on that pointer value not to change (until you call delete).

But then, how can this vector “grow” when you push more elements on it?

The answer is: through indirection:

The vector object itself might have a fixed allocated size (24 bytes in my case, but that depends). In that space it holds its own pointer(s) to extra memory. Those might get deleted and renewed when the vector is required to grow, and so that pointer will get a different value. But all of that is hidden from the user of the vector: you.

See also this question: https://stackoverflow.com/questions/34024805/c-sizeof-vector-is-24

3

While this code works, the question is why do you want a pointer to a vector? If this pointer is a class member, extra care is required with a view to the rule of three. If this is a local variable somewhere, extra care is needed to avoid leaking memory when the pointer is no longer needed.

The following code is much safer:

std::vector<int> vect;
for (size_t i = 0; i < 10; i++)
{
    std::cout << "Ptr: " << &vect << " Size " << vect.size() <<  " Max Size " << vect.capacity();
    vect.push_back(i);
    std::cout << " elements 0: " << vect[0] << ", " << i << " :" << vect[i] << std::endl; 
}

There seem to be no need to use a pointer at all in your case. Move semantics on vectors may generate code as efficient as pointers gymnastics but much safer. And there is no issue with memory leaks or dangling pointer at all.

In modern C++, the use of pointers with manual memory management (i.e. ownership of objects) should be an exception that needs to be duly justified. And if pointers are needed, smart pointers should be preferred.

4
  • @Deduplicator indeed! Thanks for the suggestion; I've edited to make this clearer.
    – Christophe
    Apr 10, 2021 at 10:38
  • Thank you very much, @Christophe. To be fair, the example I coded was nothing like what I was trying to do - it was just for helping me to see where memory was and see what happened. I 100% agree that there was no need for a pointer there. The reason for my initial attempt to use this was that I needed a function of the form ErrorEnum Foo(...., std::vector<StructType> &output_vect), but running my code through the linter failed because output_vect was not a constant reference. So, I started to turn it into a pointer, and then fell down a rabbit-hole of "do I really know the consequences?" Apr 10, 2021 at 18:38
  • 1
    @I_like_robots I'm not sure what linter you're using but sometimes the best course of action is to ignore it. Linters are not inerrant; sometimes they get confused about what you're trying to do. (Admittedly I also don't really understand the constraints here, so maybe there's a better alternative)
    – trent
    Apr 12, 2021 at 15:21
  • 2
    @I_like_robots If it’s an output reference, there is no reason for the linter to require a const. Does the compiler complain too? My advice: do not change a type just to please a linter or a compiler, without understanding why it does not accept what you want. I’ve seen the weirdest bugs on SO caused exactly by this approach.
    – Christophe
    Apr 12, 2021 at 18:41
1

A little bit more test code helped me to understand the great answers above, for anyone who may still be confused.

std::vector<int> vect({5, 6});

std::cout << "Vect address " << &vect << std::endl;
for (int i = 0; i < vect.size(); i++)
{
    std::cout << "address " << i << " elem: " << &vect[i] << std::endl;
}

std::cout << "Size " << vect.size() << " Cap " << vect.capacity() << std::endl;
vect.push_back(1);
std::cout << "Size " << vect.size() << " Cap " << vect.capacity() << std::endl;

std::cout << "Vect address " << &vect << std::endl;
for (int i = 0; i < vect.size(); i++)
{
    std::cout << "address " << i << " elem: " << &vect[i] << std::endl;
}

std::cout << "Size " << vect.size() << " Cap " << vect.capacity() << std::endl;
vect.push_back(100);
vect.push_back(101);
std::cout << "Size " << vect.size() << " Cap " << vect.capacity() << std::endl;

std::cout << "Vect address " << &vect << std::endl;
for (int i = 0; i < vect.size(); i++)
{
    std::cout << "address " << i << " elem: " << &vect[i] << std::endl;
}

This gave me the output

Vect address 0x7ffddec1b6b0
address 0 elem: 0x556617eb9e70
address 1 elem: 0x556617eb9e74
Size 2 Cap 2
Size 3 Cap 4
Vect address 0x7ffddec1b6b0
address 0 elem: 0x556617eba2a0
address 1 elem: 0x556617eba2a4
address 2 elem: 0x556617eba2a8
Size 3 Cap 4
Size 5 Cap 8
Vect address 0x7ffddec1b6b0
address 0 elem: 0x556617eba2c0
address 1 elem: 0x556617eba2c4
address 2 elem: 0x556617eba2c8
address 3 elem: 0x556617eba2cc
address 4 elem: 0x556617eba2d0

Interpreting this shows that, as @Kris Van Bael states, there is a layer of indirection between the vector and its elements - specifically, I had made the false assumption that a vector worked like an array - that the address of the vector was also the address of the first element. This is clearly not the case - the location in memory at which the vector is stored is NOT the same location at which its data elements are stored.

So, when the vector needs to grow or shrink, there is no need for its address to change - it just moves the pointer to its allocated data memory. It's pretty obvious this is necessary, but I was not considering the differences between an array and a vector.

1

When you have a class where instances could store arbitrary amounts of data, like vector or string or a map, the class instances will not contain the actual data, but just pointers to the data. So the instance itself never changes in size as you add things, only the private pointers inside it.

As a rule, the pointer to an object never changes.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.