使用 atexit() 讓程式被關閉時做對應的動作


這篇主要是紀錄 C / C++ 的 atexit() 這個函式的使用。

Heresy 這邊會有這個需求,主要是因為現在有一個自己開發的程式,使用了 freeglut 來做 OpenGL 視窗的管理,但是除了 freeglu 的主迴圈之外,還有另外自己開啟新的執行序、來建立 WebSocket 的伺服器。

在這樣的狀況下,如果 glut 建立出來的 OpenGL 視窗被關閉的話,會因為 WebSocket Server 的執行序還在執行、導致程式不會完全結束的狀況。而如果要解決這個問題,就是要在 glut 的視窗被關閉的時候,去把 WebSocket Server 關掉、讓該執行序結束才行。

由於 freeglut 似乎沒有辦法可以去攔截視窗被關閉的事件,所以後來在網路上找了一下,則在這篇 GLUT 的 FAQ 裡面,找到了使用 atexit() 這個函式的建議3.070 I have a GLUT program that allocates memory at startup. How do I deallocate this memory when the program exits?)

這邊有提到,當 glut 的視窗被關閉時,實際上 glutMainLoop() 裡面是會去執行 exit(0) 這個命令的;而如果希望可以在程式因為 exit() 命令結束之前去做某些事情的話,則是可以使用 atexit() 這個命令,來做設定的。

atexit() 這個指令的介紹,可以參考 cpluplus.com 上的介紹(連結)、或是 cppreference 上的介紹(頁面)。他基本上是 ANSI C / C++ 的函式,它的功能是去設定 function pointer、讓程式在結束前、先去執行這個指定的函式。

他被定義在 stdlib.hcstdlib)這個 header 檔裡面,介面是:

int atexit (void (*func)(void));

使用的時候,就是要先定義一個沒有參數、也沒有回傳值函式,然後再透過 atexit() 做設定。

下面就是 cpluplus.com 網頁上提供的 C 的範例:

/* atexit example */
#include <stdio.h>      /* puts */
#include <stdlib.h>     /* atexit */
  
void fnExit1 (void)
{
  puts ("Exit function 1.");
}
  
void fnExit2 (void)
{
  puts ("Exit function 2.");
}
  
int main ()
{
  atexit (fnExit1);
  atexit (fnExit2);
  puts ("Main function.");
  return 0;
}

在這個範例程式的主程式裡面,他首先是透過 atexit() 設定程式在結束前、要去執行 fnExit1()fnExit2() 這兩個函式;這兩函式基本上,都只是用來輸出偵錯用的字串而已。

而這樣的程式在執行後的結果,應該會是:

Main function.
Exit function 2.
Exit function 1.

也就是在主程式結束後、整個程式關閉前,會依序去執行 fnExit2()fnExit1() 這兩個函式。

會是這樣的結果,主要是因為 atexit() 可以設定多個函式、讓他們在程式結束前被執行;根據開發環境的實作不同,可以設定的次數也不相同,但是基本上至少可以設定 32 個。而在有設定多個的情況下,程式則是會以設定的相反順序來執行,也就是後設定的會先執行,所以在上面的例子,才會變成是先執行 fnExit2()、然後才是 fnExit1()

而如果是 C++ 的版本的話,atexit() 應該是在 std 這個 namespace 下的;這邊建議可以參考 cppreference 的範例


下面則是 Heresy 自己寫的、一個搭配 STL Thread 的測試:

#include <iostream>
#include <thread>
#include <chrono>
   
#include <cstdlib>
   
bool            g_bRunning = true;
std::thread     g_thread;
   
void inf_loop()
{
  static int i = 0;
  std::cout << "Thread start" << std::endl;
  while( g_bRunning )
  {
    ++i;
    std::cout << "T" << i << std::endl;
    std::this_thread::sleep_for( std::chrono::milliseconds(100) );
  }
  std::cout << "Thread stop" << std::endl;
}
   
void exitprogram()
{
  std::cout << "atexit" << std::endl;
  g_bRunning = false;
  g_thread.join();
}
   
int main( int argc, char** argv )
{
  std::cout << "Start" << std::endl;
  std::atexit( exitprogram );
   
  std::cout << "Start thread" << std::endl;
  g_thread = std::thread( inf_loop );
   
  std::this_thread::sleep_for( std::chrono::seconds(1) );
   
  std::cout << "End" << std::endl;
} 

這個程式會去建立一個新的執行序 g_thread、來執行 inf_loop() 這個包含了迴圈的函式,只要 g_bRunningtrue,他就會一直跑下去;而在執行 g_thread 一秒之後,則是會因為主程式結束、而執行 exitprogram() 這個函式。

exitprogram() 裡,則是會先把迴圈的 flag g_bRunning 設定成 false、讓 inf_loop() 停下來,並呼叫 g_thread.join(),等這個執行序完全結束。

而這樣的程式的執行結果,則會是:

Start
Start thread
Thread start
T1
T2
T3
T4
T5
T6
T7
T8
T9
T10
End
atexit
Thread stop

不過,不知道為什麼,上面這段程式在 gcc 上是 ok 的(但是編譯參數要加 -pthread),但是用 Visual Studio 2012 的話,程式則不會結束…不過如果改用 Visuao Studio 2010 + Boost Thread 的話,就又正常了…

對「使用 atexit() 讓程式被關閉時做對應的動作」的想法

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com 標誌

您的留言將使用 WordPress.com 帳號。 登出 /  變更 )

Google photo

您的留言將使用 Google 帳號。 登出 /  變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 /  變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 /  變更 )

連結到 %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.