はじめに
プログラムのデバッグ中、特にエラーが発生した際に、「どの関数が、どの順番で呼び出されて、今の場所にたどり着いたのか」という呼び出し履歴を知りたい場面は非常に多くあります。この関数の呼び出し階層の情報を「スタックトレース」と呼びます。
従来、C++でスタックトレースを取得するには、OS固有のAPIを使ったり、サードパーティのライブラリを使ったりする必要があり、非常に複雑でした。
この問題を解決するために、C++23では std::stacktrace
という、スタックトレースを標準機能として取得・操作するためのクラスが導入されました。
この記事では、std::stacktrace
を使って、現在の関数呼び出し履歴を簡単に取得し、表示する方法を解説します。
【前提】C++23とは?
C++23(シーピープラスにーさん)は、2023年に正式化されたC++言語の比較的新しい規格です。std::stacktrace
はこのC++23で追加された機能のため、利用するにはC++23に対応した最新のコンパイラ(と、その機能を有効にするための設定)が必要になります。
std::stacktrace
を使ったサンプルコード
このコードは、functionA
→ functionB
→ functionC
という順番で関数を呼び出し、一番深い階層の functionC
の中で、そこに至るまでのスタックトレースを取得して表示します。
完成コード
#include <iostream>
#include <stacktrace> // std::stacktrace を使うために必要
using namespace std;
// 3番目に呼ばれる関数
void functionC() {
cout << "--- functionC に到達 ---" << endl;
// 現在のスタックトレースを取得して、標準エラー出力に出力する
cout << stacktrace::current() << endl;
}
// 2番目に呼ばれる関数
void functionB() {
functionC();
}
// 最初に呼ばれる関数
void functionA() {
functionB();
}
int main() {
functionA();
return 0;
}
実行結果(コンパイラや環境により出力形式は異なります)
--- functionC に到達 ---
0# functionC() at main.cpp:11
1# functionB() at main.cpp:17
2# functionA() at main.cpp:22
3# main at main.cpp:28
... (以降、プログラムの起動に関するシステム関数が続く)
コードの解説
#include <stacktrace>
std::stacktrace
を利用するには、このヘッダーファイルをインクルードする必要があります。
cout << stacktrace::current() << endl;
これが、スタックトレースを取得・表示する核心部分です。
stacktrace::current()
: この静的メンバ関数を呼び出すと、その呼び出し時点でのスタックトレース情報を含むstd::stacktrace
オブジェクトが生成されます。cout << ...
:stacktrace
オブジェクトは、cout
のような出力ストリームに直接渡せるように設計されており、人間が読みやすい形式で整形されたスタックトレースを出力してくれます。
実行結果を見ると、functionC
が functionB
から、functionB
が functionA
から、そして functionA
が main
から呼び出された、という呼び出しの履歴が、下から上へと順番に表示されていることが分かります。
まとめ
今回は、C++23で導入された std::stacktrace
を使って、プログラムのデバッグに非常に役立つスタックトレース情報を簡単に取得する方法を解説しました。
<stacktrace>
ヘッダーをインクルードする。std::stacktrace::current()
を呼び出すだけで、その時点の呼び出し履歴が取得できる。
例外処理の catch
ブロックなどで stacktrace::current()
を使えば、エラーが発生した箇所の呼び出し階層をログに記録するといった、より高度で実用的なエラーハンドリングを実装することができます。