9

SIGFPE(signal()に渡される関数ポインターによって処理される)に依存して状態を変更し、特定の浮動小数点条件が発生したときにコードを正しく実行する数千行のアプリケーションがあります。ただし、マネージモードのC ++ / CLIでは、_control87はCで記述された静的ライブラリで実行されるSystem.ArithmeticExceptionを生成します。_fpresetおよび_control87はサポートされていません。

従来の管理されていないSIGFPE操作をC++/ CLIアプリケーションで機能させるにはどうすればよいですか?私のアプリケーションで浮動小数点が発生する場所の数は膨大になる可能性があり、他のプログラマーが何年も前に書いたすべての数値手法を完全には理解していません。

昔ながらの例外処理が、INF値ではなく、ゼロによる浮動小数点除算で機能​​するようにしたい。プラットフォーム呼び出しスタイルは機能せず、#pragma managed(off)もそのトリックを実行しません。

どのようなオプションがありますか?

4

1 に答える 1

7

ここには、非常に深刻な問題点がいくつかあります。浮動小数点例外を有効にすることは、マネージ コードの実行とはまったく互換性がありません。基本的に、JIT コンパイラーは簡単にクラッシュする可能性があります。これは、_control87() を使用するときに直面している問題です。

はい、CLR 例外が発生します。ネイティブ コードを実行するたびに、例外のバックストップが配置されます。シグナル ハンドラーは、例外が発生し、それを処理するコードがない場合にのみ呼び出されます。必然的に、C ランタイム ライブラリが例外を認識する前に、CLR が例外を認識します。したがって、SIGFPE ハンドラー呼び出しを取得することはありません。

これを狙う唯一の適切な方法は、CLR より先に例外をキャッチするラッパーを作成することです。また、FPU 制御ワードを慎重に管理することも非常に重要です。ネイティブ コードの実行中にのみ FPU 例外を有効にすることができます。これには大量のザラザラしたコードが必要であり、あまり楽しめないという事前の警告が必要です。

スニペットを投稿しなかったので、ばかげた例を作成する必要があります。

#include <Windows.h>
#include <signal.h>
#include <float.h>

#pragma managed(push, off)

double divisor;

void __cdecl fpehandler(int sig) {
    divisor = 1.0;
}

double badmath() {
    divisor = 0.0;
    return 1 / divisor;
}
#pragma managed(pop)

fpehandler() を呼び出すには、C ランタイム ライブラリ内で例外ハンドラを呼び出す必要があります。幸いなことに、それは公開されており、リンクできます。必要なのは宣言だけなので、呼び出すことができます。

// Exception filter in the CRT, it raises the signal
extern "C" int __cdecl _XcptFilter(unsigned long xcptnum, 
                                   PEXCEPTION_POINTERS pxcptinfoptrs);

浮動小数点例外に対してのみ呼び出されるようにする必要があります。したがって、例外コードに注意を払うラッパーが必要です。

int FloatingpointExceptionFilter(unsigned long xcptnum, PEXCEPTION_POINTERS pxcptinfoptrs) {
    // Only pass floating point exceptions to the CRT
    switch (xcptnum) {
        case STATUS_FLOAT_DIVIDE_BY_ZERO:
        case STATUS_FLOAT_INVALID_OPERATION:
        case STATUS_FLOAT_OVERFLOW:
        case STATUS_FLOAT_UNDERFLOW:
        case STATUS_FLOAT_DENORMAL_OPERAND:
        case STATUS_FLOAT_INEXACT_RESULT:
        case STATUS_FLOAT_STACK_CHECK:
        case STATUS_FLOAT_MULTIPLE_TRAPS:
        case STATUS_FLOAT_MULTIPLE_FAULTS:
            return _XcptFilter(xcptnum, pxcptinfoptrs);
            break;
        default:
            return EXCEPTION_CONTINUE_SEARCH;
    }
}

これで、呼び出されたシグナル ハンドラを取得する badmath() のラッパーを作成できます。

double badmathWrapper() {
    __try {
        return badmath();
    }
    __except (FloatingpointExceptionFilter(GetExceptionCode(), GetExceptionInformation())) {
    }
}

これは、任意のマネージ コードから呼び出すことができる C++/CLI クラスによって呼び出すことができます。呼び出しの前に浮動小数点例外が有効になり、呼び出しの後に再び復元されるようにする必要があります。

using namespace System;
using namespace System::Runtime::CompilerServices;

public ref class Wrapper {
public:
    static double example();
};

[MethodImplAttribute(MethodImplOptions::NoInlining)]
double Wrapper::example() {
    signal(SIGFPE, fpehandler);
    _clear87();
    unsigned oldcw = _control87(_EM_INEXACT, _MCW_EM);
    try {
        return badmathWrapper();
    }
    finally {
        _control87(oldcw, _MCW_EM);
        signal(SIGFPE, nullptr);
    }
}

_control87() の呼び出しに注意してください。これにより、「不正確な結果」を除くすべての浮動例外が有効になります。これは、コードをジットできるようにするために必要です。マスクしないと、CLR は恐ろしい死に方をし、このサイトの名前が終わるまで何度も何度も例外をスローします。うまくいけば、シグナルハンドラーはそれを必要としません。

于 2015-06-13T14:24:34.217 に答える