2

私は、C++ で作業しているコードのこのエラー ハンドラーを作成しています。明示的に渡されることなく、スタック上にあるものを何らかの形で参照できるようにしたいと考えています。具体的には、コール スタック上の関数の名前を順番に出力したいとしましょう。これは、JVM のようなマネージド ランタイム環境では些細なことですが、「単純な」コンパイル済みコードではおそらくそれほど些細なことではありません。私はこれを行うことができますか?

ノート:

  • 簡単にするために、デバッグ情報を使用して最適化なしでコードをコンパイルすると仮定します。
  • プラットフォームに依存しない、またはマルチプラットフォームの何かを書きたいと思っています。前者の方がずっと好きです。
  • 私が車輪を再発明しようとしていると思われる場合は、関連する車輪のソースにリンクしてください。そこを調べます。

アップデート:

これを行うためにどれだけ後ろにかがむ必要があるのか​​ 信じられません...言及されていない別の言語が欲しくなります。

4

3 に答える 3

1

残念ながら、標準の C++ でこれを行う組み込みの方法はありません。スタック トレーサ ユーティリティを構築するのに役立つクラスのシステムを構築できますが、トレースする各メソッドに特別なマクロを配置する必要があります。

以下に概説する戦略を使用して、それが行われた(およびその一部を実装した)のを見てきました。

  • スタック フレームに関する情報を格納する独自のクラスを定義します。少なくとも、各ノードには呼び出される関数の名前が含まれている必要があります。ファイル名/行番号情報は 2 番目に近いものです。
  • スタック フレーム ノードはリンク リストに格納され、存在する場合は再利用され、存在しない場合は作成されます。
  • 特別なオブジェクトをインスタンス化することにより、スタック フレームが作成され、リストに追加されます。オブジェクトのコンストラクターはフレーム ノードをリストに追加します。オブジェクトのデストラクタは、リストからノードを削除します。
  • 同じコンストラクター/デストラクターのペアが、スレッド ローカル ストレージにフレームのリストを作成し、作成したリストを削除します。
  • 特別なオブジェクトの構築は、マクロによって処理されます。マクロは、特殊なプリプロセッサ トークンを使用して、関数の識別情報と位置情報をフレーム クリエータ オブジェクトに渡します。

以下は、このアプローチの骨の折れる概念実証の実装です。

#include <iostream>
#include <list>

using namespace std;

struct stack_frame {
    const char *funName;
    const char *fileName;
    int line;
    stack_frame(const char* func, const char* file, int ln)
    : funName(func), fileName(file), line(ln) {}
};

thread_local list<stack_frame> *frames = 0;

struct entry_exit {
    bool delFrames;
    entry_exit(const char* func, const char* file, int ln) {
        if (!frames) {
            frames = new list<stack_frame>();
            delFrames = true;
        } else {
            delFrames = false;
        }
        frames->push_back(stack_frame(func, file, ln));
    }
    ~entry_exit() {
        frames ->pop_back();
        if (delFrames) {
            delete frames;
            frames = 0;
        }
    }
};

void show_stack() {
    for (list<stack_frame>::const_iterator i = frames->begin() ; i != frames->end() ; ++i) {
        cerr << i->funName << " - " << i->fileName << " (" << i->line << ")" << endl;
    }
}

#define FUNCTION_ENTRY entry_exit _entry_exit_(__func__, __FILE__, __LINE__);

void foo() {
    FUNCTION_ENTRY;
    show_stack();
}
void bar() {
    FUNCTION_ENTRY;
    foo();
}
void baz() {
    FUNCTION_ENTRY;
    bar();
}

int main() {
        baz();
        return 0;
}

上記のコードは C++11 でコンパイルされ、次のように出力されます。

baz - prog.cpp (52)
bar - prog.cpp (48)
foo - prog.cpp (44)

そのマクロを持たない関数は、スタック上では見えません。パフォーマンスが重要な関数には、そのようなマクロを含めるべきではありません。

これはideoneのデモです。

于 2013-08-07T15:20:10.367 に答える
0

それは簡単ではない。正確な解決策は、OS と実行環境に大きく依存します。

通常、スタックを表示することはそれほど難しくありませんが、シンボルを見つけることは通常、デバッグ シンボルを読み取ることを意味するため、非常に難しい場合があります。

別の方法は、侵入的なアプローチを使用して、「私はどこにいるのか」タイプのコードを各関数に追加することです (おそらく「デバッグ ビルドのみ」の場合)。

#ifdef DEBUG
struct StackEntry
{
   const char *file;
   const char *func;
   int         line;
   StackEntry(const char *f, const char *fn, int ln) : file(f), func(fn), line(ln) {}
};

std::stack<StackEntry> call_stack;

class FuncEntry
{
   public:
    FuncEntry(const char *file, const char *func, int line)
    {
       StackEntry se(file, func, line);
       call_stack.push_back(se);
    }
    ~FuncEntry()
    {
        call_stack.pop_back();
    }

    void DumpStack()
    {
         for(sp : call_stack)
         {
             cout << sp->file << ":" << sp->line << ": " << sp->func << "\n";
         }
    }
 };


 #define FUNC() FuncEntry(__FILE__, __func__, __LINE__); 
 #else
 #define FUNC()
 #endif


 void somefunction()
 {
     FUNC();
     ... more code here. 
 }

過去にこの手法を使用したことがありますが、このコードを入力しただけです。コンパイルできない可能性がありますが、十分に明確だと思います。主な利点の 1 つは、すべての関数に配置する必要がないことです。「重要なもの」だけです。FUNC[さまざまなレベルのデバッグに基づいて、さまざまな種類のマクロを有効または無効にすることもできます]。

于 2013-08-07T15:21:32.143 に答える