Home > Mobile >  Use of std::uninitialized_copy to an initialized memory
Use of std::uninitialized_copy to an initialized memory

Time:06-26

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());

CodePudding user response:

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.

CodePudding user response:

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.

CodePudding user response:

Uninitialized memory in C is a memory that contain no valid object(s), is obtained by call to std::get_temporary_buffer, std::aligned_storage, std::aligned_alloc, std::malloc or similar functions.

There is no way to determine if a supplied memory is initialized with objects. Developers must care of it. The compiler sanitizers can do it, but their memory attributes are not available to a program.

std::uninitialized_copy expects uninitialized memory and makes no other assumptions. Giving an initialized memory to it may result in memory leaks and undefined behavior.

Here the specialized memory algorithm uninitialized_­copy in Algorithms library in the Standard:

template<class InputIterator, class NoThrowForwardIterator>
  NoThrowForwardIterator uninitialized_copy(InputIterator first, InputIterator last,
                                            NoThrowForwardIterator result);
  1. Preconditions: result [0, (last - first)) does not overlap with [first, last).
  2. Effects: Equivalent to:
    for (; first != last;   result, (void)   first)
      ::new (voidify(*result))
        typename iterator_traits<NoThrowForwardIterator>::value_type(*first);
    
  3. Returns: result.
  • Related