1

host.cpp には次のものがあります。

int main (void)
{
    void * th = dlopen("./p1.so", RTLD_LAZY);
    void * fu = dlsym(th, "fu");

    ((void(*)(int, const char*)) fu)(2, "rofl");

    return 0;
}

そして p1.cpp は次のとおりです。

#include <iostream>

extern "C" bool fu (float * lol)
{
    std::cout << "fuuuuuuuu!!!\n";
    return true;
}

(私は意図的にエラーチェックアウトを残しました)

ホスト実行時「ふううううう!!!」完全に異なる関数シグネチャを使用してシンボルへの void ポインターを型キャストしたにもかかわらず、正しく出力されます。

なぜこれが起こったのですか?この動作は異なるコンパイラ間で一貫していますか?

4

4 に答える 4

5

これは UB が原因で発生したものであり、この動作は何らかの理由で、まったく一貫性がありません。

于 2012-09-24T20:43:27.040 に答える
4

void ポインタには関数シグネチャに関する情報がないためです。または住所以外の情報。パラメータを使い始めると、問題が発生する可能性があります。

于 2012-09-24T20:42:01.040 に答える
2

これは実際には、失敗するケースを作成する良い例ではありません。

  1. 関数の引数を使用することはありませんfu
  2. 関数は、キャスト先の関数ポインター型よりも引数が少ない (またはアクティベーション フレーム自体のメモリ フットプリントが小さい) ため、外部のメモリにアクセスしようfuとする状況に陥ることは決してありません。fu発信者によるアクティベーション レコードのセットアップ。

結局、あなたがやっていることはまだ未定義の動作ですが、問題を引き起こす可能性のある違反を作成するために何もしていないため、サイレントエラーとして終了します.

この動作は異なるコンパイラ間で一貫していますか?

いいえ。プラットフォーム/コンパイラが、呼び出し先がスタックをクリーンアップする必要がある呼び出し規約を使用している場合、おっと、呼び出し先と呼び出し元の間でアクティベーション レコードのサイズに不一致があると、おそらく困惑します。期待...呼び出し先が戻ると、スタックポインターが間違った場所に移動され、スタックが破損する可能性があり、スタックポインターの相対アドレス指定が完全に台無しになります。

于 2012-09-24T20:43:36.017 に答える
1

それはちょうど起こった、それ

  • Ccdecl呼び出し変換を使用する(呼び出し元がスタックをクリアする)
  • あなたの関数は与えられた引数引数を使用しません

あなたの呼び出し正しく機能しているようです。

しかし、実際の動作は未定義です。署名を変更したり、引数を使用すると、プログラムがクラッシュします。

追加:

たとえば、呼び出し先がスタックをクリアするstdcall変換の呼び出しを考えてみましょう。この場合、呼び出し元と呼び出し先の両方に対して正しい呼び出し変換を宣言しても、プログラムはクラッシュします。これは、スタックが破損するためです。呼び出し先は署名に従ってスタックをクリアしますが、呼び出し元は別の署名に従って埋めます。

#include <iostream>
#include <string>

extern "C" __attribute__((stdcall)) __attribute__((noinline)) bool fu (float * lol) 
{
    std::cout << "fuuuuuuuu!!!\n";
    return true;
}

void x()
{
    (( __attribute__((stdcall)) void(*)(int, const char*)) fu)(2, "rofl");
}

int main (void)
{
    void * th = reinterpret_cast<void*>(&fu);

    std::string s = "hello";

    x();

    std::cout << s;

    return 0;
}
于 2012-09-24T20:45:42.430 に答える