23

C++ での型パニング ポインター/配列の規則に興味があります。現時点で私が持っているユースケースは次のとおりです。

データのバイナリ BLOB を 32 ビット整数の配列として扱い (全体の長さが 4 の倍数であることはわかっています)、すべての値を合計してオーバーフローを無視することにより、単純な 32 ビット チェックサムを計算します。

このような関数は次のようになると思います。

uint32_t compute_checksum(const char *data, size_t size)
{
    const uint32_t *udata = /* ??? */;
    uint32_t checksum = 0;
    for (size_t i = 0; i != size / 4; ++i)
        checksum += udata[i];
    return udata;
 }

data今私が持っている質問は、変換するための「最良の」方法は何だと思いますudataか?

Cスタイルキャスト?

udata = (const uint32_t *)data

すべてのポインターが変換可能であると仮定する C++ キャスト?

udata = reinterpret_cast<const uint32_t *>(data)

C++ は、中間を使用して任意のポインター型間でそれをキャストしvoid*ますか?

udata = static_cast<const uint32_t *>(static_cast<const void *>(data))

組合を通してキャストしますか?

union {
    const uint32_t *udata;
    const char *cdata;
};
cdata = data;
// now use udata

これが 100% 移植可能なソリューションではないことは十分承知していますが、機能することがわかっている少数のプラットフォームでのみ使用することを期待しています (つまり、アラインされていないメモリ アクセスとポインター エイリアシングに関するコンパイラの仮定)。あなたは何をお勧めします?

4

4 に答える 4

12

C ++標準に関する限り、litbの答えは完全に正しく、最も移植性があります。Cスタイルのキャスト、、、またはを介して、にキャストすると、厳密const char *dataなエイリアシング規則に違反します(厳密なエイリアシングについてを参照。完全に最適化してコンパイルすると、コードが正しく機能しない可能性が高くなります。const uint3_t *static_castreinterpret_cast

ユニオン(litbなどmy_reint)を介してキャストするのがおそらく最善の解決策ですが、あるメンバーを介してユニオンに書き込み、別のメンバーを介して読み取ると、未定義の動作が発生するという規則に技術的に違反します。ただし、実質的にすべてのコンパイラがこれをサポートしており、期待どおりの結果が得られます。どうしても標準の100%に準拠したい場合は、ビットシフト方式を使用してください。それ以外の場合は、ユニオンを介してキャストすることをお勧めします。これにより、パフォーマンスが向上する可能性があります。

于 2008-12-06T20:57:26.813 に答える
6

コードを簡単にするために、効率を無視します。

#include <numeric>
#include <vector>
#include <cstring>

uint32_t compute_checksum(const char *data, size_t size) {
    std::vector<uint32_t> intdata(size/sizeof(uint32_t));
    std::memcpy(&intdata[0], data, size);
    return std::accumulate(intdata.begin(), intdata.end(), 0);
}

char が署名されている可能性があるため、追加のマスクが必要だと思うことを除いて、各 char を順番にシフトする litb の最後の回答も気に入っています。

checksum += ((data[i] && 0xFF) << shift[i % 4]);

型のしゃれが問題になる可能性がある場合、私はしゃれを安全に入力しようとするよりも、しゃれを入力しないことを好みます。そもそも異なる型のエイリアス化されたポインターを作成しない場合は、コンパイラーがエイリアスで何をするかを心配する必要はありません。また、ユニオンを介して複数の static_casts を確認するメンテナンス プログラマーも心配する必要はありません。

余分なメモリをあまり割り当てたくない場合は、次のようにします。

uint32_t compute_checksum(const char *data, size_t size) {
    uint32_t total = 0;
    for (size_t i = 0; i < size; i += sizeof(uint32_t)) {
        uint32_t thisone;
        std::memcpy(&thisone, &data[i], sizeof(uint32_t));
        total += thisone;
    }
    return total;
}

十分な最適化により、gcc で memcpy と余分な uint32_t 変数が完全に取り除かれ、整列されていない整数値が読み取られます。これは、プラットフォームで最も効率的な方法がソース配列から直接行われる場合に限ります。他の「真面目な」コンパイラにも同じことが当てはまることを願っています。しかし、このコードは litb のコードよりも大きくなったので、uint64_t と同じように機能する関数テンプレートに変換する方が簡単であり、私のコードはほとんど選択するのではなく、ネイティブのエンディアンとして機能すること以外は、言うべきことはあまりありません。 -エンディアン。

もちろん、これは完全に移植可能ではありません。sizeof(uint32_t) char のストレージ表現が、希望する方法で uin32_t のストレージ表現に対応していると想定しています。これは、一方を他方として「扱う」ことができると述べているため、質問によって暗示されています。エンディアン性、char が 8 ビットであるかどうか、および uint32_t がそのストレージ表現ですべてのビットを使用するかどうかは、明らかに侵入する可能性がありますが、問題はそれらが侵入しないことを意味します。

于 2008-12-07T13:41:05.477 に答える
0

私の50セントがあります-それを行うさまざまな方法。

#include <iostream>
#include <string>
#include <cstring>

    uint32_t compute_checksum_memcpy(const char *data, size_t size)
    {
        uint32_t checksum = 0;
        for (size_t i = 0; i != size / 4; ++i)
        {
            // memcpy may be slow, unneeded allocation
            uint32_t dest; 
            memcpy(&dest,data+i,4);
            checksum += dest;
        }
        return checksum;
    }

    uint32_t compute_checksum_address_recast(const char *data, size_t size)
    {
        uint32_t checksum = 0;
        for (size_t i = 0; i != size / 4; ++i)
        {
            //classic old type punning
            checksum +=  *(uint32_t*)(data+i);
        }
        return checksum;
    }

    uint32_t compute_checksum_union(const char *data, size_t size)
    {
        uint32_t checksum = 0;
        for (size_t i = 0; i != size / 4; ++i)
        {
            //Syntax hell
            checksum +=  *((union{const char* c;uint32_t* i;}){.c=data+i}).i;
        }
        return checksum;
    }

    // Wrong!
    uint32_t compute_checksum_deref(const char *data, size_t size)
    {
        uint32_t checksum = 0;
        for (size_t i = 0; i != size / 4; ++i)
        {
            checksum +=  *&data[i];
        }
        return checksum;
    }

    // Wrong!
    uint32_t compute_checksum_cast(const char *data, size_t size)
    {
        uint32_t checksum = 0;
        for (size_t i = 0; i != size / 4; ++i)
        {
            checksum +=  *(data+i);
        }
        return checksum;
    }


int main()
{
    const char* data = "ABCDEFGH";
    std::cout << compute_checksum_memcpy(data, 8) << " OK\n";
    std::cout << compute_checksum_address_recast(data, 8) << " OK\n";
    std::cout << compute_checksum_union(data, 8) << " OK\n";
    std::cout << compute_checksum_deref(data, 8) << " Fail\n";
    std::cout << compute_checksum_cast(data, 8) << " Fail\n";
}
于 2015-11-29T00:58:05.787 に答える
-3

私はこのスレッドがしばらく非アクティブになっていることを知っていますが、この種のもののための簡単な一般的なキャストルーチンを投稿すると思いました:

// safely cast between types without breaking strict aliasing rules
template<typename ReturnType, typename OriginalType>
ReturnType Cast( OriginalType Variable )
{
    union
    {
        OriginalType    In;
        ReturnType      Out;
    };

    In = Variable;
    return Out;
}

// example usage
int i = 0x3f800000;
float f = Cast<float>( i );

それが誰かを助けることを願っています!

于 2011-10-12T12:48:48.407 に答える