C++ 信號處理

您好,有抱負的程式設計師!今天,我們將深入探討C++信號處理的精彩世界。如果您完全是程式設計的新手,也請不用擔心——我會逐步引導您,就像我過去幾年教學中對無數學生所做的那樣。讓我們一起踏上這次旅程!

C++ Signal Handling

信號是什麼?

在我們深入細節之前,先來了解信號是什麼。在電腦的世界中,信號就像小警報或通知,告訴程式有重要的事情發生了。這就像您的手機震動以通知您收到了消息一樣。這些信號可以由操作系統或其他程式發出。

signal() 函數

現在,讓我們來談談我們的第一位明星:signal() 函數。這個函數就像您程式的個人助手。它幫助您的程式決定在收到特定信號時要做什麼。

如何使用 signal()

以下是 signal() 函數的基本語法:

#include <csignal>

signal(signalNumber, signalHandler);

讓我們來分解一下:

  • signalNumber:这是我们感兴趣的信號类型。
  • signalHandler:當收到信號時將運行的函數。

一個簡單的例子

讓我們看一個簡單的例子來看看這是如何工作的:

#include <iostream>
#include <csignal>

void signalHandler(int signum) {
std::cout << "收到中斷信號 (" << signum << ").\n";
exit(signum);
}

int main() {
signal(SIGINT, signalHandler);

while(true) {
std::cout << "程式正在運行..." << std::endl;
sleep(1);
}

return 0;
}

在這個例子中:

  1. 我們定義了一個 signalHandler 函數,它打印一條消息並退出程式。
  2. main() 中,我們使用 signal(SIGINT, signalHandler) 來告訴我們的程式在收到 SIGINT 信號時運行 signalHandler(通常在您按下 Ctrl+C 時發出)。
  3. 然後我們有一個無窮迴圈使程式保持運行。

如果您運行此程式並按 Ctrl+C,您將看到 signalHandler 的消息在程式退出之前。

raise() 函數

現在,讓我們來認識我們的第二位明星:raise() 函數。如果 signal() 是設定鬧鐘,那麼 raise() 就像按鬧鐘按鈕一樣。

如何使用 raise()

raise() 的語法更簡單:

#include <csignal>

raise(signalNumber);

在這裡,signalNumber 是您要發送的信號類型。

使用 raise() 的例子

讓我們修改我們之前的例子以使用 raise()

#include <iostream>
#include <csignal>

void signalHandler(int signum) {
std::cout << "收到信號 (" << signum << ").\n";
}

int main() {
signal(SIGTERM, signalHandler);

std::cout << "發送 SIGTERM 信號..." << std::endl;
raise(SIGTERM);

std::cout << "回到 main 函數。" << std::endl;

return 0;
}

在這個例子中:

  1. 我們設定 signalHandler 處理 SIGTERM 信號。
  2. main() 中,我們使用 raise(SIGTERM) 將 SIGTERM 信號發送到我們自己的程式。
  3. 這會觸發 signalHandler,它打印一條消息。
  4. 處理完信號後,程式繼續運行並打印最後一條消息。

常見的信號類型

讓我們來看看您可能遇到的一些常見信號類型:

信號 描述
SIGABRT 非正常終止
SIGFPE 浮點數異常
SIGILL 非法指令
SIGINT CTRL+C 中斷
SIGSEGV 段違反
SIGTERM 終止請求

最佳實踐和提示

  1. 小心使用全局變量:信號處理器應該小心訪問全局變量,因為它們可能處於不一致的狀態。
  2. 保持簡單:信號處理器應該盡可能簡單。在信號處理器中進行複雜的操作可能會導致意外的行為。
  3. 對共享變量使用易變:如果您有在主要代碼和信號處理器之間共享的變量,請將它們聲明為 volatile
  4. 記住,信號是異步的:信號可能會在任何時間到達,所以設計程式時要考慮這一點。

一個更進階的例子

讓我們透過一個稍微複雜一點的例子將它們 all 放在一起:

#include <iostream>
#include <csignal>
#include <unistd.h>

volatile sig_atomic_t gSignalStatus = 0;

void signalHandler(int signum) {
gSignalStatus = signum;
}

int main() {
// 註冊信號處理器
signal(SIGINT, signalHandler);
signal(SIGTERM, signalHandler);

std::cout << "程式啟動。按 Ctrl+C 中斷。" << std::endl;

while(gSignalStatus == 0) {
std::cout << "正在工作..." << std::endl;
sleep(1);
}

std::cout << "收到信號 " << gSignalStatus << "。正在清理..." << std::endl;
// 在這裡執行必要的清理工作

return 0;
}

在這個例子中:

  1. 我們使用全局變量 gSignalStatus 來跟踪收到的信號。
  2. 我們註冊了 SIGINT 和 SIGTERM 的處理器。
  3. 程式運行直到收到信號,然後它進行清理並退出。

這展示了在需要清理資源後退出的程式中信號處理的更現實的用法。

結論

就這樣,各位!我們已經穿越了C++信號處理的土地,從 signal() 的基礎到主動的 raise(),甚至還接觸了一些更進階的概念。記住,就像學習任何新技能一樣,掌握信號處理需要練習。如果一開始沒有立即領悟,也請不要氣餒——每一位偉大的程式設計師都是從您現在的位置開始的。

在我們結束的時候,我想起了我曾經有一位學生,她最初在這個概念上遇到了困難。她曾經說過,信號就像是試圖捕捉無形的蝴蝶。但是通過練習和堅持,她不僅掌握了這個概念,還為她公司的軟體開發了一個強大的錯誤處理系統。所以,堅持下去,誰知道呢?下一個偉大的軟體創新可能就來自於您!

編程愉快,願您的信號總能得到優雅的處理!

Credits: Image by storyset