使用 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 的話,就又正常了…

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *