在单个语句中移动和使用唯一指针是否有效?
Is it valid to move and use unique pointer in single statement?
我在下面的代码行中使用堆栈跟踪遇到分段错误,如下所述。 self
在这里是 unique_ptr
。
self->socket.async_send_to(self->frame->get_asio_buffer(), self->client_endpoint,
std::bind(&download_server::receiver, std::move(self), std::placeholders::_1,
std::placeholders::_2));
堆栈跟踪。 #3 是上面提到的代码行
==1652==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000090 (pc 0x55e763e1bdc9 bp 0x7ffc2242d380 sp 0x7ffc2242d370 T0)
==1652==The signal is caused by a READ memory access.
==1652==Hint: address points to the zero page.
#0 0x55e763e1bdc9 in std::__shared_ptr<tftp::frame, (__gnu_cxx::_Lock_policy)2>::get() const /usr/include/c++/10.2.0/bits/shared_ptr_base.h:1325
#1 0x55e763e1a9d5 in std::__shared_ptr_access<tftp::frame, (__gnu_cxx::_Lock_policy)2, false, false>::_M_get() const /usr/include/c++/10.2.0/bits/shared_ptr_base.h:1024
#2 0x55e763e1948d in std::__shared_ptr_access<tftp::frame, (__gnu_cxx::_Lock_policy)2, false, false>::operator->() const /usr/include/c++/10.2.0/bits/shared_ptr_base.h:1018
#3 0x55e763e0d665 in tftp::download_server::sender(std::unique_ptr<tftp::download_server, std::default_delete<tftp::download_server> >&, boost::system::error_code const&, unsigned long) ../src/tftp_server.cpp:50
#4 0x55e763e0cf56 in tftp::download_server::serve(boost::asio::io_context&, std::shared_ptr<tftp::frame const> const&, boost::asio::ip::basic_endpoint<boost::asio::ip::udp> const&) ../src/tftp_server.cpp:27
#5 0x55e763e0da65 in spin_tftp_server(boost::asio::io_context&, std::shared_ptr<tftp::frame const> const&, boost::asio::ip::basic_endpoint<boost::asio::ip::udp> const&) ../src/tftp_server.cpp:97
#6 0x55e763e0e3f5 in tftp::distributor::perform_distribution_cb(boost::system::error_code const&, unsigned long const&) ../src/tftp_server.cpp:142
#7 0x55e763e273fe in void std::__invoke_impl<void, void (tftp::distributor::*&)(boost::system::error_code const&, unsigned long const&), std::shared_ptr<tftp::distributor>&, boost::system::error_code const&, unsigned long const&>(std::__invoke_memfun_deref, void (tftp::distributor::*&)(boost::system::error_code const&, unsigned long const&), std::shared_ptr<tftp::distributor>&, boost::system::error_code const&, unsigned long const&) /usr/include/c++/10.2.0/bits/invoke.h:73
在上面的代码中,self
在一个语句中被使用和移动。有效吗?如果是,那么 self
的使用顺序是什么?
跟踪文件顶部的代码(文件/usr/include/c++/10.2.0/bits/shared_ptr_base.h)
1322 /// Return the stored pointer.
1323 element_type*
1324 get() const noexcept
1325 { return _M_ptr; }
1326
根据第 1322 行的评论,上述用法似乎无效。在运行时,它无法找到唯一指针指向的数据。这个读数正确吗?
但是,如果上述假设是正确的,那么下面的示例代码也应该会崩溃。这里 self->t.async_wait
做了类似的事情(在同一个语句中移动和使用)但是这没有任何问题。
#include <iostream>
#include <functional>
#include <boost/asio.hpp>
class looper {
public:
static void create(boost::asio::io_context &io){
std::unique_ptr<looper> self = std::make_unique<looper>(io);
start(self, boost::system::error_code());
}
static void start(std::unique_ptr<looper> &self, const boost::system::error_code e){
std::cout << "Round :" << self->count << std::endl;
if(self->count-- == 0){
return;
}
self->t.expires_after(boost::asio::chrono::seconds(1));
self->t.async_wait(std::bind(&looper::start, std::move(self), std::placeholders::_1));
}
looper(boost::asio::io_context &io) : t(io) {
std::cout << "Construction" << std::endl;
}
~looper() {
std::cout << "Destruction" << std::endl;
}
boost::asio::steady_timer t;
uint32_t count = 3;
};
void f(boost::asio::io_context &io){
looper::create(io);
}
int main() {
boost::asio::io_context io;
f(io);
io.run();
return 0;
}
示例程序的输出
[root@archlinux cpp]# g++ unique_async.cpp -lpthread -fsanitize=address -Wpedantic
[root@archlinux cpp]# ./a.out
Construction
Round :3
Round :2
Round :1
Round :0
Destruction
[root@archlinux cpp]#
如果 std::move
是问题,那么为什么第一种情况会崩溃,但示例程序运行没有任何问题。
move(self)
本身没有问题。它是对 bind
的嵌套调用和访问 self
的其他参数的组合。对 bind
的调用在其他参数访问之前清空 self
。
您有两个展示柜:
// bad
self->socket.async_send_to(self->frame->get_asio_buffer(),
self->client_endpoint,
bind(&download_server::receiver, move(self), _1, _2));
// good
self->t.async_wait(bind(&looper::start, move(self), _1));
在 C++17 之前,这两种情况都有未定义的行为,因为参数是按未指定的顺序计算的,而且也是未排序的。这包括函数调用之前的对象表达式(在 (
之前)。
从 C++17 开始,参数表达式的顺序不确定,但对象表达式的顺序在其他参数之前。结果是:
- 在糟糕的情况下,
bind
可以在其他参数之前调用,这将清除 self
,在评估其他参数时导致空指针访问。
- 然而,在好的情况下,对象表达式首先被调用,而
self
仍然有效,然后 bind
被调用,这清除了 self
;但由于没有其他参数需要评估,调用没问题。
我在下面的代码行中使用堆栈跟踪遇到分段错误,如下所述。 self
在这里是 unique_ptr
。
self->socket.async_send_to(self->frame->get_asio_buffer(), self->client_endpoint,
std::bind(&download_server::receiver, std::move(self), std::placeholders::_1,
std::placeholders::_2));
堆栈跟踪。 #3 是上面提到的代码行
==1652==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000090 (pc 0x55e763e1bdc9 bp 0x7ffc2242d380 sp 0x7ffc2242d370 T0)
==1652==The signal is caused by a READ memory access.
==1652==Hint: address points to the zero page.
#0 0x55e763e1bdc9 in std::__shared_ptr<tftp::frame, (__gnu_cxx::_Lock_policy)2>::get() const /usr/include/c++/10.2.0/bits/shared_ptr_base.h:1325
#1 0x55e763e1a9d5 in std::__shared_ptr_access<tftp::frame, (__gnu_cxx::_Lock_policy)2, false, false>::_M_get() const /usr/include/c++/10.2.0/bits/shared_ptr_base.h:1024
#2 0x55e763e1948d in std::__shared_ptr_access<tftp::frame, (__gnu_cxx::_Lock_policy)2, false, false>::operator->() const /usr/include/c++/10.2.0/bits/shared_ptr_base.h:1018
#3 0x55e763e0d665 in tftp::download_server::sender(std::unique_ptr<tftp::download_server, std::default_delete<tftp::download_server> >&, boost::system::error_code const&, unsigned long) ../src/tftp_server.cpp:50
#4 0x55e763e0cf56 in tftp::download_server::serve(boost::asio::io_context&, std::shared_ptr<tftp::frame const> const&, boost::asio::ip::basic_endpoint<boost::asio::ip::udp> const&) ../src/tftp_server.cpp:27
#5 0x55e763e0da65 in spin_tftp_server(boost::asio::io_context&, std::shared_ptr<tftp::frame const> const&, boost::asio::ip::basic_endpoint<boost::asio::ip::udp> const&) ../src/tftp_server.cpp:97
#6 0x55e763e0e3f5 in tftp::distributor::perform_distribution_cb(boost::system::error_code const&, unsigned long const&) ../src/tftp_server.cpp:142
#7 0x55e763e273fe in void std::__invoke_impl<void, void (tftp::distributor::*&)(boost::system::error_code const&, unsigned long const&), std::shared_ptr<tftp::distributor>&, boost::system::error_code const&, unsigned long const&>(std::__invoke_memfun_deref, void (tftp::distributor::*&)(boost::system::error_code const&, unsigned long const&), std::shared_ptr<tftp::distributor>&, boost::system::error_code const&, unsigned long const&) /usr/include/c++/10.2.0/bits/invoke.h:73
在上面的代码中,self
在一个语句中被使用和移动。有效吗?如果是,那么 self
的使用顺序是什么?
跟踪文件顶部的代码(文件/usr/include/c++/10.2.0/bits/shared_ptr_base.h)
1322 /// Return the stored pointer.
1323 element_type*
1324 get() const noexcept
1325 { return _M_ptr; }
1326
根据第 1322 行的评论,上述用法似乎无效。在运行时,它无法找到唯一指针指向的数据。这个读数正确吗?
但是,如果上述假设是正确的,那么下面的示例代码也应该会崩溃。这里 self->t.async_wait
做了类似的事情(在同一个语句中移动和使用)但是这没有任何问题。
#include <iostream>
#include <functional>
#include <boost/asio.hpp>
class looper {
public:
static void create(boost::asio::io_context &io){
std::unique_ptr<looper> self = std::make_unique<looper>(io);
start(self, boost::system::error_code());
}
static void start(std::unique_ptr<looper> &self, const boost::system::error_code e){
std::cout << "Round :" << self->count << std::endl;
if(self->count-- == 0){
return;
}
self->t.expires_after(boost::asio::chrono::seconds(1));
self->t.async_wait(std::bind(&looper::start, std::move(self), std::placeholders::_1));
}
looper(boost::asio::io_context &io) : t(io) {
std::cout << "Construction" << std::endl;
}
~looper() {
std::cout << "Destruction" << std::endl;
}
boost::asio::steady_timer t;
uint32_t count = 3;
};
void f(boost::asio::io_context &io){
looper::create(io);
}
int main() {
boost::asio::io_context io;
f(io);
io.run();
return 0;
}
示例程序的输出
[root@archlinux cpp]# g++ unique_async.cpp -lpthread -fsanitize=address -Wpedantic
[root@archlinux cpp]# ./a.out
Construction
Round :3
Round :2
Round :1
Round :0
Destruction
[root@archlinux cpp]#
如果 std::move
是问题,那么为什么第一种情况会崩溃,但示例程序运行没有任何问题。
move(self)
本身没有问题。它是对 bind
的嵌套调用和访问 self
的其他参数的组合。对 bind
的调用在其他参数访问之前清空 self
。
您有两个展示柜:
// bad
self->socket.async_send_to(self->frame->get_asio_buffer(),
self->client_endpoint,
bind(&download_server::receiver, move(self), _1, _2));
// good
self->t.async_wait(bind(&looper::start, move(self), _1));
在 C++17 之前,这两种情况都有未定义的行为,因为参数是按未指定的顺序计算的,而且也是未排序的。这包括函数调用之前的对象表达式(在 (
之前)。
从 C++17 开始,参数表达式的顺序不确定,但对象表达式的顺序在其他参数之前。结果是:
- 在糟糕的情况下,
bind
可以在其他参数之前调用,这将清除self
,在评估其他参数时导致空指针访问。 - 然而,在好的情况下,对象表达式首先被调用,而
self
仍然有效,然后bind
被调用,这清除了self
;但由于没有其他参数需要评估,调用没问题。