- 1. 問題
- 2. 考察的要點
- 3. 解決策略
- 3.1. 方案一:使用GCC的擴充功能
- 3.2. 方案二:使用全域性變數
- 3.3. 方案三:atexit
- 4. Demo測試
- 4.1. 測試程式碼
- 4.2. 執行結果
- 5. 程式異常退出場景
- 5.1. 存在的問題
- 5.2. 解決方案
- 5.2.1. 原理
- 5.2.2. 示例程式碼
- 5.2.3. 執行結果
- 5.2.4. 特殊說明
- 6. 參考文件
1. 問題
我們知道C/C++程式的執行邏輯是從main函式開始,到main函式結束。但是,有時我們需要在main函式開始之前或結束之後執行一段邏輯,比如:
- 如何在main函式開始之前執行一段邏輯?
- 如何在main函式結束之後執行一段邏輯?
有辦法實現嗎?在往下閱讀之前,請先思考一下。
2. 考察的要點
C++程式的程式碼執行邏輯。
全域性變數|靜態變數的理解。
3. 解決策略
3.1. 方案一:使用GCC的擴充功能
GCC編譯器的擴充功能,透過 __attribute__
關鍵字註冊“在main函式開始之前或結束之後”執行的回撥函式。
__attribute((constructor)) void before_main() {
std::cout << "before main" << std::endl;
}
__attribute((destructor)) void after_main() {
std::cout << "after main" << std::endl;
}
3.2. 方案二:使用全域性變數
全域性變數會在程序剛啟動的時候就初始化,在程序結束的時候被銷燬。所以:全域性物件的初始化會在main函式執行之前被執行;全域性物件的銷燬會在main函式執行之後被執行。
結合C++類的建構函式和虛構函式的特點,可以專門定義一個類來處理main函式開始之前和結束之後的邏輯(為了保證這個類只有一個全域性物件,建議將這個類設計成單例模式),然後在main之前宣告這個類的一個全域性變數。
class BeforeAndAfterMain
{
public:
static BeforeAndAfterMain& GetInstance()
{
static BeforeAndAfterMain instance;
return instance;
}
~BeforeAndAfterMain()
{
std::cout << "Global object destory after main" << std::endl;
}
private:
BeforeAndAfterMain()
{
std::cout << "Global object construct before main" << std::endl;
}
BeforeAndAfterMain(const BeforeAndAfterMain&) = delete;
BeforeAndAfterMain& operator=(const BeforeAndAfterMain&) = delete;
};
auto& g_before_and_after_main = BeforeAndAfterMain::GetInstance();
3.3. 方案三:atexit
針對main函式結束之後的邏輯,可以使用atexit函式註冊一個回撥函式,在main函式執行之後被執行。
#include <cstdlib>
void at_main_exit(){
std::cout << "at_main_exit" << std::endl;
}
4. Demo測試
4.1. 測試程式碼
完整測試程式碼如下:
#include <iostream>
#include <cstdlib>
__attribute((constructor)) void before_main() {
std::cout << "before main" << std::endl;
}
__attribute((destructor)) void after_main() {
std::cout << "after main" << std::endl;
}
class BeforeAndAfterMain
{
public:
static BeforeAndAfterMain& GetInstance()
{
static BeforeAndAfterMain instance;
return instance;
}
~BeforeAndAfterMain()
{
std::cout << "Global object destory after main" << std::endl;
}
private:
BeforeAndAfterMain()
{
std::cout << "Global object construct before main" << std::endl;
}
BeforeAndAfterMain(const BeforeAndAfterMain&) = delete;
BeforeAndAfterMain& operator=(const BeforeAndAfterMain&) = delete;
};
auto& g_before_and_after_main = BeforeAndAfterMain::GetInstance();
void at_main_exit(){
std::cout << "at_main_exit" << std::endl;
}
int main() {
// https://en.cppreference.com/w/cpp/header/cstdlib
atexit(at_main_exit);
std::cout << "main begin" << std::endl;
int a = 10;
int b = 5;
// crash to exit
// int b = 0;
int c = a / b;
std::cout << "a /b = " << c << std::endl;
std::cout << "main end" << std::endl;
return 0;
}
4.2. 執行結果
before main
Global object construct before main
main begin
a /b = 2
main end
at_main_exit
Global object destory after main
after main
5. 程式異常退出場景
5.1. 存在的問題
上面的Demo,把
int b = 5;
替換成
// crash to exit
int b = 0;
會導致程式異常(除數不能為0)退出,輸出如下:
before main
Global object construct before main
main begin
Floating point exception
三種main函式結束後的邏輯均未被執行。說明:程式異常退出時(如:crash),“main函式結束後的邏輯均”不被執行,不能cover住這種場景。
5.2. 解決方案
5.2.1. 原理
當程式崩潰時,作業系統會傳送一個訊號給程式,通知它發生了異常。在 C++中,可以透過 signal 函式來註冊一個訊號處理程式,使程式能夠在接收到該訊號時執行自定義的程式碼。
程式的執行流程:
- 執行程式,按正常邏輯執行。
- 程式崩潰,異常退出,根據不同的崩潰原因,作業系統能識別出不同的崩潰訊號(signal)。
- 作業系統傳送對應的崩潰訊號(signal)給執行程式。
- 執行程式根據提前已註冊好的訊號處理函式,執行對應的訊號處理邏輯。
- 訊號處理函式執行完畢,透過exit函式退出程式。
這樣保證了:雖然程式的主流程崩潰了,但是程式還是能正常結束。這樣即使程式崩潰了,還是能夠自己完成如:“資源釋放”、“狀態儲存或重置”等一些重要的邏輯。
5.2.2. 示例程式碼
void signal_handler(int sig) {
// 這裡編寫你的異常訊號處理邏輯,比如列印日誌,儲存狀態,捕獲堆疊資訊等。
std::cerr << "signal_handler" << std::endl;
// 注意:訊號處理程式執行完成,一定要呼叫exit退出,否則訊號處理函式可能會被迴圈執行。
exit(1);
}
int main() {
// 註冊訊號處理函式
// signal(SIGSEGV, signal_handler);
signal(SIGFPE, signal_handler);
// https://en.cppreference.com/w/cpp/header/cstdlib
atexit(at_main_exit);
std::cout << "main begin" << std::endl;
int a = 10;
// int b = 5;
// crash to exit
int b = 0;
int c = a / b;
std::cout << "a /b = " << c << std::endl;
std::cout << "main end" << std::endl;
return 0;
}
5.2.3. 執行結果
before main
Global object construct before main
main begin
signal_handler
at_main_exit
Global object destory after main
after main
5.2.4. 特殊說明
- 當程式崩潰時,可能已經無法正常執行程式碼,因此需要謹慎地編寫訊號處理程式,以避免進一步的崩潰或資料損壞。
- 訊號處理程式執行完成,一定要呼叫exit退出,否則訊號處理函式可能會被迴圈執行。
- 考慮各種可能出現的異常訊號,比如:SIGSEGV、SIGFPE、SIGILL、SIGABRT等。這些可能出現的異常,都需要註冊對應的訊號處理程式。以免出現異常漏捕獲的情況。
6. 參考文件
https://blog.csdn.net/zhizhengguan/article/details/122623008
https://blog.csdn.net/MldXTieJiang/article/details/129620160