0

This issue is very strange, I have following code:

#include <iostream>
#include <thread>
#include <chrono>
#include <string>
#include <vector>

struct Data {
    Data(const char * d)
        :data(d){}
    std::string data;
};

// Using these data to avoid SOO(aka small object optimize) ASAP
std::vector<Data> arr {"11111111111111111111111111111111111111111111111111111111",
                        "2222222222222222222222222222222222222222222222222222222",
                        "3333333333333333333333333333333333333333333333333333333",
                        "4444444444444444444444444444444444444444444444444444444",
                        "5555555555555555555555555555555555555555555555555555555"};

class Test {
public:
  void test() {
    // I get a valid iterator first
    std::vector<Data>::iterator it = arr.begin();

    // I run a thread to clear arr
    std::thread([](){
          std::this_thread::sleep_for(std::chrono::seconds(2));
          arr.clear();
        }).detach();
    // sleep 3s to make following code run after thread
    std::this_thread::sleep_for(std::chrono::seconds(3));
    // I think it->data is a invalid operation, Howerver, it works :(
    std::string res = it->data;
  }
};

int main() {
  Test test;
  test.test();
  std::this_thread::sleep_for(std::chrono::seconds(5));
  return 0;
}

This case shows visiting a iterator after calling clear(), theoretically it is a invalid operation, but it works, I very wonder it, why why why???

mac.ma
  • 703
  • 2
  • 8
  • 22
  • 9
    Undefined behavior is undefined. Just because the vector has been cleared doesn't mean it's memory has been released. Even if the memory _has_ been released, the data is still there until something else comes along and uses that memory. – Miles Budnek Apr 03 '23 at 00:41
  • 1
    See also [Undefined, unspecified and implementation-defined behavior](https://stackoverflow.com/q/2397984/11082165) – Brian61354270 Apr 03 '23 at 00:42
  • @MilesBudnek since the vector was never declared as volatile, the compiler may even be using bits of it that were cached from before the `clear`! You really can't rely on anything once undefined behavior hits. – Mark Ransom Apr 03 '23 at 01:44
  • 1
    After the call to `clear()` all iterators are invalid. See [vector::clear()](https://cplusplus.com/reference/vector/vector/clear/). Using them is undefined behavior. – Martin York Apr 03 '23 at 04:44

1 Answers1

1

using clear() erases all the elements and makes all following size() calls return zero

however, it does not change the capacity of the vector

Note that undefined behavior may do some weird things

notabot
  • 40
  • 6
  • 1
    `clear()` calls the vector's content's destructors. They are only required to release any dynamic memory the objects own. Compilers/libraries differ as to anything beyond that. MSVC tends to alter the string objects so they have 0 length. Probably to prevent badly written code from failing at the cost of unnecessary code gen. UB either way if used. – doug Apr 03 '23 at 01:31
  • 1
    The answer is incomplete. Note that you assume, that the code the compiler creates is sane and seems to assume, that a sleep will synchronize the data. But in fact even the compiler would be allowed to duplicate this vector, because it's UB. Also the fact that the vector is cleared is, without some sort of fence or synchronisation, not required to be visible from other threads. It may even be, that iterator, memory and the controlblock is untouched. – Fabian Keßler Apr 03 '23 at 01:37
  • @doug all stls must alter the string content, because a string is defined to hold a c(zero terminated) string. Therefore at least the first char must be overriden by zero. – Fabian Keßler Apr 03 '23 at 01:41
  • 1
    @FabianKeßler once an object is deleted, there are absolutely no constraints on its former contents. Nobody cleared the strings themselves. – Mark Ransom Apr 03 '23 at 01:56
  • 1
    @FabianKeßler Not so. See [this](https://godbolt.org/z/6EMdeEjMq) Clang doesn't change a thing when the string object is destructed. MSVC, OTOH, changes it to the same thing that a `std::move` produces. Waste of code for a dtor. Example code contains technical UB but matches memory dumps. – doug Apr 03 '23 at 03:14
  • @doug I shouldn't answer something when I am tired. Somehow I thought you meant that the strings content is not altered after calling `clear` on a string. – Fabian Keßler Apr 03 '23 at 07:59