C++のシグナル処理入門:signal()関数とraise()関数の使い方を徹底解説
シグナル(Signal)とは、オペレーティングシステム(OS)からプロセスへ送られる割り込み(インタラプト)のことで、プログラムを意図せず途中で強制終了させてしまうことがあります。UNIX、Linux、Mac OS X、Windowsなどの環境では、Ctrl+Cキーを押すことで割り込みを発生させることができます。
すべてのシグナルがプログラム側で捕捉できるわけではありませんが、以下の表に示すシグナルについては、プログラム内で捕捉し、シグナルの種類に応じた適切な処理を実行することが可能です。これらのシグナルは、C++のヘッダーファイル<csignal>で定義されています。
| シグナル | 説明 |
|---|---|
| SIGABRT | プログラムの異常終了。abort()関数の呼び出しなどが該当します。 |
| SIGFPE | 誤った算術演算。ゼロ除算やオーバーフローを引き起こす演算など。 |
| SIGILL | 不正な命令の検出。 |
| SIGINT | 対話型の割り込みシグナル(Ctrl+Cなど)の受信。 |
| SIGSEGV | ストレージ(メモリ)への不正なアクセス。 |
| SIGTERM | プログラムに対して送信された終了要求。 |
signal()関数とは
C++のシグナル処理ライブラリには、予期しないイベントを捕捉するためのsignal()関数が用意されています。signal()関数の構文は次のとおりです。
void (*signal (int sig, void (*func)(int)))(int);
簡潔に説明すると、この関数は2つの引数を受け取ります。第1引数はシグナル番号を表す整数値、第2引数はシグナルハンドラー(シグナル処理)関数へのポインターです。
それでは、signal()関数を使ってSIGINTシグナルを捕捉する簡単なC++プログラムを作成してみましょう。プログラム内で捕捉したいシグナルがある場合は、必ずsignal()関数を使ってそのシグナルを登録し、対応するシグナルハンドラーと関連付ける必要があります。以下の例を見てみましょう。
コード例:SIGINTシグナルの捕捉
#include <iostream>
#include <csignal>
using namespace std;
void signalHandler( int signum ) {
cout << "割り込みシグナル (" << signum << ") を受信しました。\n";
// ここでクリーンアップ処理やリソースの解放を行う
// プログラムを終了する
exit(signum);
}
int main () {
// シグナルSIGINTとシグナルハンドラーを登録
signal(SIGINT, signalHandler);
while(1) {
cout << "スリープ中...." << endl;
sleep(1);
}
return 0;
}
実行結果
スリープ中.... スリープ中.... スリープ中....
ここでCtrl+Cを押すと、出力は次のように変化します。
実行結果(Ctrl+C押下後)
スリープ中.... スリープ中.... スリープ中.... 割り込みシグナル (2) を受信しました。
raise()関数とは
raise()関数を使用すると、プログラムの内部からシグナルを発生させることができます。この関数は整数のシグナル番号を引数として受け取り、その構文は以下のとおりです。
int raise (signal sig);
引数sigには、送信したいシグナル番号を指定します。指定できるのは、SIGINT、SIGABRT、SIGFPE、SIGILL、SIGSEGV、SIGTERM、SIGHUPなどのシグナルです。次の例では、raise()関数を使ってプログラム内部からシグナルを発生させています。
コード例:raise()関数によるシグナル発生
#include <iostream>
#include <csignal>
using namespace std;
void signalHandler( int signum ) {
cout << "割り込みシグナル (" << signum << ") を受信しました。\n";
// ここでクリーンアップ処理やリソースの解放を行う
// プログラムを終了する
exit(signum);
}
int main () {
int i = 0;
// シグナルSIGINTとシグナルハンドラーを登録
signal(SIGINT, signalHandler);
while(++i) {
cout << "スリープ中...." << endl;
if( i == 3 ) {
raise( SIGINT );
}
sleep(1);
}
return 0;
}
実行結果
スリープ中.... スリープ中.... スリープ中.... 割り込みシグナル (2) を受信しました。
まとめ
C++では、signal()関数でシグナルハンドラーを登録することにより、Ctrl+Cなどの外部からの割り込みにも、raise()関数による内部的なシグナル発生にも柔軟に対応できます。シグナル処理を適切に実装すれば、プログラム終了時のリソース解放や安全なシャットダウン処理を実現でき、より堅牢なアプリケーションの構築につながります。
-
C++の例外処理入門!try・catch・throwの基本と標準例外クラス一覧
C++における例外処理(Exception Handling)とは、プログラムの実行時に発生するエラー(ランタイムエラー)を適切に扱うための仕組みです。例外とは、実行時に送出(スロー)されるイベントのことで、C++ではすべての標準例外が std::exception クラスから派生しています。もし例外を捕捉しなかった場合、例外メッセージが出力され、プログラムは強制終了してしまいます。 C++標準では、例外関連のクラスが <exception> ヘッダに定義されており、プログラムの中で自由に利用できます。親子関係によるクラス階層は次のようになっています。 C++の主な標準例外クラス
-
C++のファイル処理におけるtellp()関数の使い方を解説
C++のファイル処理において、tellp()関数は出力ストリームと組み合わせて使用される関数で、ストリーム内のポインタ(書き込み位置)の現在位置を返します。戻り値は整数データ型であり、ストリームポインタが現在指している位置を表します。 tellp()メソッドは引数を一切取らず、以下のように記述します。 pos_type tellp(); アルゴリズム tellp()を使用したファイル処理の基本的な流れは以下の通りです。 fstreamクラスのオブジェクト(ここでは newfile)を作成します。 open()メソッドを呼び出し、オブジェクト newfile を使って「tpoint.txt」