3

Below is an example quoted from C++ Concurrency in Action $2.2

void f(int i,std::string const& s);
void oops(int some_param)
{
  char buffer[1024];
  sprintf(buffer, "%i",some_param);
  std::thread t(f,3,buffer);
  t.detach();
}  

And the author says it's undefined behavior:

In this case, it’s the pointer to the local variable buffer that’s passed through to the new thread, and there’s a significant chance that the function oops will exit before the buffer has been converted to a std::string on the new thread, thus leading to undefined behavior. The solution is to cast to std::string before passing the buffer to the std::thread constructor:

void f(int i,std::string const& s);
void not_oops(int some_param)
{
  char buffer[1024];
  sprintf(buffer,"%i",some_param);
  std::thread t(f,3,std::string(buffer));
  t.detach();
}   

But I'm confused about that. Because as far as I'm concernd, there's no difference between the two forms:

In the first code snippet,when passing buffer to the function as parameter,it will also generate a temporary std::string object,and bind function parameter to that temporary object.So it's exactly the same with the second code snippet.The only difference is that temporary object in former one is generated by compiler implicitly and latter one by user explicitly.

choxsword
  • 3,187
  • 18
  • 44
  • 2
    Arrays decay to pointers when passed as function arguments. Objects are copied (unless it's a reference argument), so the thread gets its own copy of the string. – Barmar Apr 26 '18 at 06:27
  • 1
    There is copying, `decay_copy` in fact. The thread is created with a pointer. The actual invocation (and parameter construction) can happen much later. – StoryTeller - Unslander Monica Apr 26 '18 at 06:30
  • 1
    This answer may help you to understand this: https://stackoverflow.com/questions/29693630/details-in-the-process-of-constructing-a-stdthread-object – BartekPL Apr 26 '18 at 06:32
  • 1
    What you're missing is that it's the *value* that's passed to the thread. In one case, the value is a pointer to something that will no longer exist. In the other case, the value is in fact what you want the thread to have. – David Schwartz Apr 26 '18 at 06:34
  • The thread constructor accepts a parameter pack. When an array is passed, it is passed as a pointer to the first element - and the array can cease to exist after `oops()` returns, and dereferencing the pointer after that gives undefined behaviour. When a `std::string` is passed, a new object is created that contains a copy of the string data, and passed as an rvalue reference - so the object (with the data it carries) is moved to the thread constructor rather than ceasing to exist. – Peter Apr 26 '18 at 06:36
  • @StoryTeller What is `decay copy` and when does it happens? It's the first time I see this term and could not find it on stackoverflow.Should I raise a new question about that? – choxsword Apr 26 '18 at 06:42
  • 1
    @bigxiao - [See here](http://en.cppreference.com/w/cpp/thread/thread/thread). It's how the thread turns the references it is passed into values. – StoryTeller - Unslander Monica Apr 26 '18 at 06:44
  • @StoryTeller I'm confused.There is no copying operation in `decay_copy`, and just `forward` operation. What did I miss? What's more, there's two `forward` operation, which makes me completely puzzled. – choxsword Apr 26 '18 at 06:54
  • 1
    @bigxiao - The return type of `decay_copy` is a value, and it will be copy initialized (into the storage T.C. mentions, with help from RVO). – StoryTeller - Unslander Monica Apr 26 '18 at 06:55
  • 1
    @bigxiao - Though it's important to note that `decay_copy` as described there is for conceptually explaining things. There may not be an *actual* `decay_copy`, just some equivalent operation. – StoryTeller - Unslander Monica Apr 26 '18 at 06:58

1 Answers1

5

There are two steps here:

  • Whatever you pass to std::thread's constructor are decay-copied by that constructor into storage accessible by the new thread.
  • The new thread then invokes the callable object using those copied arguments.

The first snippet copies a pointer to the buffer. Invoking the new thread's callable object will then convert that pointer to a std::string, but by then the buffer may be gone already.

The second snippet performs the conversion in the original thread, when it's guaranteed to be valid. The temporary std::string is then moved by the std::thread constructor into the intermediate storage.

T.C.
  • 133,968
  • 17
  • 288
  • 421