全局静态对象销毁后是否可以调用操作系统计时器回调
Is it possible for an OS timer callback to be invoked after global static object destruction?
我将更简单地重新表述这个问题,在之前的版本没有获得太多吸引力之后,使用更简单的MCVE。
我已经形成了这样的印象,在main()
结束后,所发生的一切都是全局对象破坏,然后是静态对象破坏,按顺序排列。
我从来没有考虑过其他";"东西";在CCD_ 2结束和过程结束之间的这段时间内发生。但我最近一直在使用Linux定时器,实验性地,似乎定时器的回调可以在";后期";在进程的main()
退出之后,甚至在静态全局对象被销毁之后。
问题:该评估正确吗?静态全局对象被破坏后,是否可以调用计时器回调?
我从来没有想过会发生什么";"迟到";在进程的生命周期中。我想我天真地认为";某事""阻止"正在发生的事情";在CCD_ 4退出之后。
问题:我的计时器回调使用了一个静态全局对象——目的是让对象";总是";无论何时调用回调,都要随时待命。但是,如果可以在静态全局对象被破坏后调用定时器回调,那么这种策略是不安全的。是否有一种众所周知/正确的方法来处理这一问题:即防止计时器回调访问无效对象/内存?
下面的代码创建";许多";计时器设置为在未来2秒过期,其回调引用静态全局对象。main()
在调用计时器回调的中间退出。cout
s显示静态全局对象已被销毁,而计时器回调仍在被调用。
// main.cpp
#include <algorithm>
#include <cerrno>
#include <csignal>
#include <cstring>
#include <iostream>
#include <map>
#include <mutex>
#include <string>
#include <unistd.h>
using namespace std;
static int tmp = ((srand ( time( NULL ) )), 0);
class Foo { // Encapsulates a random-sized, random-content string.
public:
Foo() {
uint32_t size = (rand() % 24) + 1;
std::generate_n( std::back_inserter( s_ ), size, randChar );
}
void operator=( const Foo& other ) { s_ = other.s_; }
std::string s_;
private:
static char randChar() { return ('a' + rand() % 26); }
};
class GlobalObj { // Encapsulates a map<timer_t, Foo>.
public:
~GlobalObj() { std::cout << __FUNCTION__ << std::endl; }
Foo* getFoo( const timer_t& timer ) {
Foo* ret = NULL;
{
std::lock_guard<std::mutex> l( mutex_ );
std::map<timer_t, Foo*>::iterator i = map_.find( timer );
if ( map_.end() != i ) {
ret = i->second;
map_.erase( i );
}
}
return ret;
}
void setFoo( const timer_t& timer, Foo* foo ) {
std::lock_guard<std::mutex> l( mutex_ );
map_[timer] = foo;
}
private:
std::mutex mutex_;
std::map<timer_t, Foo*> map_;
};
static GlobalObj global_obj; // static global GlobalObj instance.
void osTimerCallback( union sigval sv ) { // The timer callback
timer_t* timer = (timer_t*)(sv.sival_ptr);
if ( timer ) {
Foo* foo = global_obj.getFoo(*timer);
if ( foo ) {
cout << "timer[" << *timer << "]: " << foo->s_ << endl;
delete foo;
}
delete timer;
}
}
bool createTimer( const struct timespec& when ) { // Creates an armed timer.
timer_t* timer = new timer_t;
struct sigevent se;
static clockid_t clock_id =
#ifdef CLOCK_MONOTONIC
CLOCK_MONOTONIC;
#else
CLOCK_REALTIME;
#endif
memset( &se, 0, sizeof se );
se.sigev_notify = SIGEV_THREAD;
se.sigev_value.sival_ptr = timer;
se.sigev_notify_function = osTimerCallback;
if ( timer_create( clock_id, &se, timer ) ) {
cerr << "timer_create() err " << errno << " " << strerror( errno ) << endl;
return false;
}
{
struct itimerspec its;
memset( &its, 0, sizeof its );
its.it_value.tv_sec = when.tv_sec;
its.it_value.tv_nsec = when.tv_nsec;
if ( timer_settime( *timer, 0, &its, NULL ) ) {
cerr << "timer_settime err " << errno << " " << strerror( errno ) << endl;
return false;
}
global_obj.setFoo( *timer, new Foo );
}
return true;
}
int main( int argc, char* argv[] ) { // Creates many armed timers, then exits
static const struct timespec when = { 2, 0 };
for ( uint32_t i = 0; i < 100; ++i ) {
createTimer( when );
}
usleep( 2000010 );
return 0;
}
示例错误:
$ g++ --version && g++ -g ./main.cpp -lrt && ./a.out
g++ (Debian 6.3.0-18+deb9u1) 6.3.0 20170516
Copyright (C) 2016 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
timer[timer[~GlobalObj0x55b34c17bd700x55b34c17be60
]: gx
*** Error in `./a.out': double free or corruption (fasttop): 0xtimer[0x55b34c17bf50]: wsngolhdjvhx
]: npscgelwujjfp
Aborted
注意,该错误提到";双自由";;上面的代码有两个delete
语句:删除它们似乎不会影响问题的再现性。我认为,由于访问无效内存,错误消息只是转移注意力。
将main()
中的usleep()
增加到足够大,以便允许在静态全局对象销毁之前发生所有计时器回调调用,从而始终成功执行。
不,没有任何魔法可以阻止计时器在main
结束后启动。
在C++中防止类似情况发生的常见方法是为每种需要手动释放的资源类型创建一个小的资源拥有类。参见RAII和三/五/零规则。
这样一个类的基础可能是这样的:
#include <cerrno> // errno
#include <cstring> // std::strerror
#include <stdexcept> // std::runtime_error
#include <string> // std::string
#include <utility> // std::exchange
class Timer {
public:
Timer(clockid_t clockid, sigevent& sev) {
if(timer_create(clockid, &sev, &timerid))
throw std::runtime_error(std::string("timer_create: ") +
std::strerror(errno));
}
// rule of 5
Timer(const Timer&) = delete; // no copy construction
Timer(Timer&& rhs) : // move construction ok
timerid(std::exchange(rhs.timerid, nullptr)) {}
Timer& operator=(const Timer&) = delete; // no copy assignment
Timer& operator=(Timer&& rhs) { // move assignment ok
if(this != &rhs) {
if(timerid) timer_delete(timerid);
timerid = std::exchange(rhs.timerid, nullptr);
}
return *this;
}
~Timer() {
if(timerid) timer_delete(timerid);
}
private:
timer_t timerid;
};
现在,您可以将Timer
存储在容器中,当容器超出范围时,它们将被正确删除。
每当必须处理这些create
/delete
对中的一个时,使用这种方法通常会限制像您得到的这种惊喜的数量,这些对通常在C API:s中找到。
另请阅读有关静态初始化顺序Fiasco的信息,以避免其他潜在的陷阱。
注意:这个实现利用了timer_t
是我的系统上的指针类型这一事实,我不知道是否总是这样
- 什么时候调用组成单元对象的析构函数
- 对RValue对象调用的LValue ref限定成员函数
- 为什么使用 "this" 指针调用派生成员函数?
- 函数调用中参数的顺序重要吗
- OpenGL - 在抛出"__gnu_cxx::recursive_init_error"实例后终止调用?
- 基于另一个成员参数将函数调用从类传递给它的一个成员
- 为什么我的C#代码在调用回C++COM直到Task时会暂停.等待/线程.加入
- 在c++类上调用void函数
- 为什么 std::unique 不调用 std::sort?
- 调用专用模板时出错"no matching function for call to [...]"
- 选择要调用的构造函数
- C++为什么尽管我调用了void函数,它却不起作用
- 构造函数正在调用一个使用当前类类型的函数
- 变量没有改变?通过向量的函数调用
- 没有为自己的结构调用列表推回方法
- 调用'begin(int [n])'没有匹配函数
- 什么时候调用析构函数
- 如何用参数值调用函数(仅在运行时已知)
- std::cout.imbue()多重调用
- 函数何时会在c++中包含stack_Unwind_Resume调用