Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use of std::uninitialized_copy to an initialized memory

If std::uninitialized_copy is used to an initialized memory, does this use cause a memory leak or an undefined behavior?

For example:

std::vector<std::string> u = {"1", "2", "3"};
std::vector<std::string> v = {"4", "5", "6"};
// What happens to the original elements in v?
std::uninitialized_copy(u.begin(), u.end(), v.begin());
like image 959
Jeongu Kim Avatar asked Nov 16 '25 03:11

Jeongu Kim


2 Answers

TL;DR: Don't do this.

Assuming your std::string implementation uses SSO1 (all modern ones do, I believe), then this doesn't leak anything only if the strings are short enough to be SSO'ed.

Any possible UB here is governed by [basic.life]/5:

A program may end the lifetime of an object of class type without invoking the destructor, by reusing or releasing the storage as described above.

[Note 3: A delete-expression ([expr.delete]) invokes the destructor prior to releasing the storage. — end note]

In this case, the destructor is not implicitly invoked and any program that depends on the side effects produced by the destructor has undefined behavior.

It's not entirely clear to me what "depending on side effects" entails (can you solemnly declare that you don't mind the lack of side effects, and get rid of UB this way?), but destroying a SSO'ed string should have no side effects.

But! If you enable iterator debugging, then the destructor might get side effects regardless of SSO (to somehow notify the iterators that they should be invalidated). Then skipping the destructor might be problematic.


1 SSO = small (or short) string optimization = not allocating a short string on the heap, and instead embedding it directly into the std::string instance.

like image 179
HolyBlackCat Avatar answered Nov 17 '25 17:11

HolyBlackCat


Base on specialized.algorithms.general and uninitialized.copy I don't find any formal requirements that the destination range must be uninitialized. As such, we can consider the effect of std::uninitialized_copy which is defined as equivalent to the follow (excluding the implied exception safety boilerplate code) :

for (; first != last; ++result, (void) ++first)
  ::new (voidify(*result))
    typename iterator_traits<NoThrowForwardIterator>::value_type(*first);

We can conclude that std::uninitialized_copy does not call any destructor or otherwise cares about what was previously located in the destination range. It simply overwrites it, assuming it is uninitialized.

To figure out what this means in terms of correctness, we can refer to basic.life

A program may end the lifetime of an object of class type without invoking the destructor, by reusing or releasing the storage as described above. In this case, the destructor is not implicitly invoked and any program that depends on the side effects produced by the destructor has undefined behavior.

This however uses the loosely defined notion of "any program that depends on the side effects of". What does it mean to "depend on the side effects of"?

If your question was about overwriting vectors of char or int there would be no problem, as these are not class types and do not have destructors so there can be no side effects to depend on. However std::string's destructor may have the effect of releasing resources. std::basic_string may have addition, more directly observable side effects if a user defined allocator is used. Note that in the case of a range containing non-class type elements std::uninitialized_copy is not required. These elements allow for vacuous initialization and simply copying them to uninitialized storage with std::copy is fine.

Since I don't believe it is possible for the behavior of a program to depend on the release of std::string's resources, I believe the code above is correct in terms of having well defined behavior, though it may leak resources. An argument could be made that the behavior might rely on std::bad_alloc eventually being thrown, but std::string isn't strictly speaking required to dynamically allocate. However, if the type of element used had side effects which could influence the behavior, and the program depended on those effects, then it would be UB.

In general, while it may be well defined in some cases, the code shown violates assumptions on which RAII is based, which is a fundamental feature most real programs depend on. On these grounds std::uninitialized_copy should not be used to copy to a range which already contains objects of class type.

like image 26
François Andrieux Avatar answered Nov 17 '25 18:11

François Andrieux



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!