1

単精度データの配列を、余分なバイトを処理するために配列が適切なサイズになっていることを使用して、倍精度にプロモートするルーチンがあります。

void dpromote(const int n, double *x)             
{
    for (int i = n; i --> 0 ;) {    
        x[i] = ((float *)x)[i];
    }
}

入口にxn floatsが含まれ、出口にはn doublesが含まれます。

void test_dpromote()
{
    double  e[]   = {1, 2, 3, 4, 5, 6, 7};
    float   x[]   = {1, 2, 3, 4, 5, 6, 7, 0, 0, 0, 0, 0, 0, 0};
    const int n = sizeof(e)/sizeof(e[0]);
    dpromote(n, (void *) x);
    /* memcmp(x, e, sizeof(e)) will return 0 when this works as expected */
}

なぜ私はこれをしているのですか?数値が多いコード内の混合精度の反復的な改良。この質問の目的のために、あなたはその理由を本当に無関係であるとして無視することができます。

複数のコンパイラはdpromote、厳密なエイリアシングが有効になっているさまざまな積極的な最適化レベルのロジックで問題ありません。最近、あるコンパイラベンダー(名前は不明のまま)は、ループのインデックスを再作成して、後方トラバーサルではなく、メモリを介した前方トラバーサルになるようにすることを決定しました。コードを30分見つめると、ループ変換によってまったくのゴミが発生することがわかります。

すべてのC99厳密なエイリアスのベルとホイッスルが有効になっているdpromoteロジックは、未定義の動作に依存していますか?コードが未定義のことをしていない限り、コンパイラがループのインデックスを変更しても問題ないと考える理由がわかりません。

4

2 に答える 2

0

はい、厳密なエイリアシングルールに違反しています。ユニオンを使用します-元のレイアウトを維持できない場合がありますが、意図がはるかによく反映され、一般的にクリーンになります。

#include <stdio.h>

union value 
{
    double d;
    float f;
};

void dpromote (const int n, value* x)
{
    for (int i=0; i < n; ++i)
        x[i].d = x[i].f;
}

void test_dpromote()
{
    value x[] = {{.f=1}, {.f=2}, {.f=3}, {.f=4}, {.f=5}, {.f=6}, {.f=7}};
    const int n = sizeof(x) / sizeof(x[0]);

    for (int i=0; i < n; ++i)
        printf("float: %f\n", x[i].f);

    dpromote(n, x);

    for (int i=0; i < n; ++i)
        printf("double: %f\n", x[i].d);
}

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

元のレイアウトを維持する必要がある場合は、厳密なエイリアシングルールを満たすために、メモリのブロックを手動で管理する必要があります。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

double* dpromote (const int n, char* x)
{
    for (int i=n; i-- > 0; )
    {
        float f;
        memcpy(&f, &x[i*sizeof(float)], sizeof(f));
        double d = f;
        memcpy(&x[i*sizeof(double)], &d, sizeof(d));
    }

    return (double*)x;
}

void test_dpromote()
{
    int const n = 7;
    char* block = (char*)malloc(n*sizeof(double));
    for (int i=0; i < n; ++i)
    {
        float const x = i+1;
        memcpy(&block[i*sizeof(float)], &x, sizeof(x));
    }

    // It is now safe to access block through x

    float* x = (float*)block;
    for (int i=0; i < n; ++i)
        printf("float: %f\n", x[i]);

    double* y = dpromote(n, block);
    for (int i=0; i < n; ++i)
        printf("double: %f\n", y[i]);

    // It is now safe to access block through y, however
    // subsequent access through x will violate strict aliasing rules
}

int main ()
{
    test_dpromote();
    return 0;
}
于 2013-02-21T06:57:32.943 に答える
0

標準では、コンパイラが有用ではない可能性が高い場所で悲観的なエイリアシングの仮定を行うことを義務付けていますが、他の種類のエイリアシングの証拠を無視するようにコンパイラを招待していると解釈されます。floatとして読み取られたすべての要素が以前にfloatとして書き込まれ、それを「double」として書き込む操作のにシーケンスされた場合、コードは動作を定義したと思います。ただし、割り当てが重複するオブジェクトの読み取りと書き込みを行うため、コードは最後の反復でUBを呼び出すと思います。

あなたのコードは次のように書かれていましたか?

void dpromote(const int n, double *x)             
{
    float *fp = x;
    for (int i = n; i --> 0 ;) {    
        double d = fp[i];
        x[i] = d;
    }
}

ループ内にエイリアシングが発生する可能性を示唆する証拠がないため、エイリアシングに気付かなかったコンパイラを特に非難することはありません。ループを再配置するコンパイラの動作には熱心ではありませんが、読み取りと書き込みが重複しているため、他の理由で誤動作したコンパイラに障害はありません[たとえば、FPUのないCPUをターゲットとするコンパイラがクリアのようなことをした場合「double」のビットを作成し、その一部を元の指数と仮数のビットシフトバージョンで上書きしました]。ただし、割り当てを読み取りと書き込みに分割してもコンパイラの動作が修正されない場合、そのような動作は標準にも常識にも従わないため、危険と見なします。

于 2017-10-05T20:56:51.277 に答える