這篇主要是紀錄 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.h(cstdlib)這個 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_bRunning 是 true,他就會一直跑下去;而在執行 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 的話,就又正常了…