[英]Strange behavior when calling std::invoke(std::forward(…)) with address-sanitization in a std::thread with a std::ref
I am trying to pass a lambda-closure to std::thread
that calls arbitrary closed-over function with arbitrary closed-over arguments.我正在尝试将 lambda-closure 传递给std::thread
,该线程调用任意封闭 function 和任意封闭 arguments。
template< class Function, class... Args >
std::thread timed_thread(Function&& f, Args&&... args) {
// Regarding capturing perfectly-forwarded variables in lambda, see [1]
auto thread_thunk = ([&] {
std::cout << "Start thread timer" << std::endl;
// Regarding std::invoke(_decay_copy(...), ...), see (3) of [2].
// Assume no exception can be thrown from copying.
std::invoke(_decay_copy(std::forward<Function>(f)),
_decay_copy(std::forward<Args>(args)...));
}
}
int main() {
int i = 3;
std::thread t = timed_thread(&print_int_ref, std::ref(i));
t.join()
return 0;
}
/*
[1]: https://stackoverflow.com/questions/26831382/capturing-perfectly-forwarded-variable-in-lambda
[2]: https://en.cppreference.com/w/cpp/thread/thread/thread
*/
std::forward
so that r-value references and l-value references get forwarded (dispatched correctly).我使用std::forward
以便转发右值引用和左值引用(正确调度)。std::invoke
and the lambda create temporary data-structures, the caller must wrap references in std::ref
.因为std::invoke
和 lambda 创建临时数据结构,调用者必须将引用包装在std::ref
中。 The code appears to work, but causes stack-use-after-scope
with address sanitization.该代码似乎可以工作,但会导致stack-use-after-scope
并进行地址清理。 This is my primary confusion.这是我的主要困惑。
I think this may be related to this error , but I do not see the relation since I am not returning a reference;我认为这可能与这个错误有关,但我没有看到这种关系,因为我没有返回参考; The reference to i
should be valid for the duration of main
's stack-frame which should outlast the thread because main
joins on it.对i
的引用应该在main
的堆栈帧的持续时间内有效,该堆栈帧应该比线程长,因为main
加入了它。 The reference is passed by copies ( std::reference_wrapper
) into the thread_thunk
.引用通过副本( std::reference_wrapper
)传递到thread_thunk
。
I suspect args...
cannot be captured by reference, but then how should it be captured?我怀疑args...
不能通过引用捕获,但是应该如何捕获呢?
A secondary confusion: changing {std::thread t = timed_thread(blah); t.join();}
次要混淆:更改{std::thread t = timed_thread(blah); t.join();}
{std::thread t = timed_thread(blah); t.join();}
(braces to force destructor) to timed_thread(blah).join();
{std::thread t = timed_thread(blah); t.join();}
(强制析构函数的大括号)到timed_thread(blah).join();
incurs no such problem, even though to me they appear equivalent.不会产生这样的问题,即使对我来说它们看起来是等价的。
#include <functional>
#include <iostream>
#include <thread>
template <class T>
std::decay_t<T> _decay_copy(T&& v) { return std::forward<T>(v); }
template< class Function, class... Args >
std::thread timed_thread(Function&& f, Args&&... args) {
// Regarding capturing perfectly-forwarded variables in lambda, see [1]
auto thread_thunk = ([&] {
std::cout << "Start thread timer" << std::endl;
// Regarding std::invoke(_decay_copy(...), ...), see (3) of [2].
// Assume no exception can be thrown from copying.
std::invoke(_decay_copy(std::forward<Function>(f)),
_decay_copy(std::forward<Args>(args)...));
std::cout << "End thread timer" << std::endl;
});
/* The single-threaded version code works perfectly */
// thread_thunk();
// return std::thread{[]{}};
/* multithreaded version appears to work
but triggers "stack-use-after-scope" with ASAN */
return std::thread{thread_thunk};
}
void print_int_ref(int& i) { std::cout << i << std::endl; }
int main() {
int i = 3;
/* This code appears to work
but triggers "stack-use-after-scope" with ASAN */
// {
// std::thread t = timed_thread(&print_int_ref, std::ref(i));
// t.join();
// }
/* This code works perfectly */
timed_thread(&print_int_ref, std::ref(i)).join();
return 0;
}
Compiler command: clang++ -pthread -std=c++17 -Wall -Wextra -fsanitize=address test.cpp &&./a.out
.编译器命令: clang++ -pthread -std=c++17 -Wall -Wextra -fsanitize=address test.cpp &&./a.out
。 Remvoe address
to see it work.删除address
以查看它的工作。
Both versions appear to be undefined behavior.两个版本似乎都是未定义的行为。 It is potluck whether the undefined behavior will be caught by the sanitizer.未定义的行为是否会被消毒剂捕获是便饭。 It is fairly likely that even the allegedly working version will also trip the sanitizer, if the program is rerun sufficient amount of times.如果程序重新运行足够多的次数,即使是所谓的工作版本也很可能会触发消毒剂。 The bug is here:错误在这里:
std::thread timed_thread(Function&& f, Args&&... args) {
// Regarding capturing perfectly-forwarded variables in lambda, see [1]
auto thread_thunk = ([&] {
The closure uses the captured args
by reference .闭包通过引用使用捕获的args
。
As you know, the parameters to timed_thread
go out of scope and get destroyed when timed_thread
returns.如您所知, timed_thread
go 的参数来自 scope 并在timed_thread
返回时被销毁。 That's their scope.那是他们的 scope。 That's how C++ works.这就是 C++ 的工作原理。
But you have no guarantees, whatsoever , that this closure gets executed by the new execution thread and references the captured, by reference , all the args...
, before they vanish in a puff of smoke here:但是你不能保证,这个闭包会被新的执行线程执行并引用捕获的,通过引用,所有的args...
,然后它们在这里消失得无影无踪:
return std::thread{thread_thunk};
Unless this new thread manages to execute the code inside thread_hunk
, that references the captured, by reference args...
, it will end up accessing after this function returns, and this results in undefined behavior.除非这个新线程设法执行thread_hunk
内的代码,该代码引用捕获的,通过引用args...
,否则它将在此 function 返回后结束访问,这将导致未定义的行为。
The object being used after its lifetime is the std::ref(i).在其生命周期之后使用的 object 是 std::ref(i)。 Follow the references.按照参考。 The function takes the std::ref by reference, the lambda captures by reference, the lambda is copied into the newly created thread which copies the reference to the std::ref(i). function 通过引用获取 std::ref,lambda 通过引用捕获,lambda 被复制到新创建的线程中。复制引用到 std::refi)
The working version is working because the lifetime of std::ref(i) ends at the semicolon, and the thread is joined before then.工作版本正在工作,因为 std::ref(i) 的生命周期以分号结束,并且线程在此之前加入。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.