[英]Is it possible for an OS timer callback to be invoked after global static object destruction?
我正在更簡單地重新表述這個問題,並且在先前版本沒有獲得太多關注之后使用更簡單的 MCVE。
我的印象是在main()
結束后,所有發生的都是全局 object 銷毀,然后是 static object 銷毀,順序是這樣。
我從來沒有考慮過在main()
結束和進程結束之間的這段時間內發生其他“東西”的可能性。 但我最近一直在使用 Linux 計時器,並且通過實驗,似乎可以在進程的這個“后期”期間調用計時器的回調,在main()
退出之后,甚至在 static 全局對象被銷毀之后。
問:這樣的評價對嗎? 是否可以在 static 個全局對象銷毀后調用定時器回調?
我從來沒有想過在一個過程的生命周期中這個“晚期”會發生什么。 我想在main()
退出后我會天真地假設“某事”“阻止”“事情發生”。
問題:我的計時器回調使用 static 全局 object -- 目的是 object 將“始終”存在,無論何時調用回調。 但是,如果在 static 個全局對象被銷毀后可以調用定時器回調,那么該策略是不安全的。 有沒有一種眾所周知/正確的方法來處理這個問題:即防止定時器回調訪問無效的對象/內存?
下面的代碼創建了“許多”計時器,這些計時器設置為在未來 2 秒后過期,其回調引用 static 全局 object。main main()
在計時器回調被調用的中間退出。 cout
顯示 static 全局 object 被銷毀,而定時器回調仍在被調用。
// 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
請注意,錯誤中提到“double free”; 上面的代碼有兩個delete
語句:刪除它們似乎不會影響問題的重現性。 我相信錯誤消息是一個轉移注意力的問題,因為訪問無效的 memory。
將main()
中的usleep()
) 增加到足夠大,以便允許所有計時器回調調用在 static 全局 object 銷毀導致始終成功執行之前發生。
不,沒有魔法可以阻止定時器在main
結束后觸發。
在 C++ 中防止這種事情發生的常見方法是為每種需要手動釋放的資源創建一個擁有 class 的小資源。 請參閱RAII和三/五/零規則。
這種 class 的基礎可能如下所示:
#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
存儲在容器中,當容器超出 scope 時,它們將被正確刪除。
每當必須處理這些create
/ delete
對之一(通常在 C API:s 中找到)時,采用這種方法通常會限制像您得到的那樣的驚喜數量。
另請閱讀Static Initialization Order Fiasco以避免其他潛在陷阱。
注意:此實現利用了timer_t
在我的系統上是指針類型這一事實,我不知道情況是否總是如此。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.