When reading a book about c multi-thread programming, I came across one example below.
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();
}
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.
I know that it is the char* type argument buffer, not std::string is copied into the internal storage of thread t. What confuses me is that there's a chance the conversion from char* to std::string has not taken place even when thread t is already constructed. So when will the conversion take place? Is it just before the thread is scheduled to execute by the OS?
CodePudding user response:
As you are saying the array argument is decayed to a char*
, which is then copied for the thread. To be more specific the newly created thread executes basically the following expression:
std::invoke(auto(std::forward<F>(f)), auto(std::forward<Args>(args))...)
where f
and args
are the parameters of the std::thread
constructor and auto(/*...*/)
is the new C 23 syntax which creates a prvalue of the decayed type of the argument from the argument (and materializes a temporary object from it when being bound to the reference in the parameter of std::invoke
).
However, in this evaluation the materialization of the temporary objects auto(/*...*/)
is performed in the context of the caller of the thread constructor and the beginning of the call to std::invoke
synchronizes with the completion of the std::thread
constructor.
That means the auto(/*...*)
decayed argument copies are made before the thread starts execution, but everything else happens as part of the new thread of execution.
So auto(buffer)
will give a temporary object of type char*
which lives until the end of the thread function invocation (because temporary objects live until the end of the full expression in which they are created). Then std::invoke
is called with a reference to this temporary object as argument. This invocation happens in the newly created thread of execution and everything from here happens unsynchronized with the rest of the execution in the original thread.
std::invoke
basically calls the copied f
given to it as first argument and forwards the other arguments to f
. Now since your f
expects a std::string const&
as second parameter, but std::invoke
will pass it a char*
as argument, a temporary std::string
will be materialized, initialized from the char*
, for the reference to bind to. This construction happens therefore in the context of the std::invoke
call, which is already unsynchronized with the potential destruction, in the main thread, of the array to which the pointer points.