web-dev-qa-db-ja.com

ポインターにバイトオフセットを追加するためのポータブルで安全な方法

私はC++での作業に非常に慣れていないため、言語の複雑さと微妙さをすべて把握していません。

C++ 11の任意の型のポインターに任意のバイトオフセットを追加するための、最もポータブルで正確かつ安全な方法は何ですか?

SomeType* ptr;
int offset = 12345 /* bytes */;
ptr = ptr + offset;             // <--

Stack OverflowとGoogleで多くの答えを見つけましたが、それらはすべて異なることを提案しています。私が遭遇したいくつかのバリアント:

  1. _char * にキャスト:

    ptr = (SomeType*)(((char*)ptr) + offset);
    
  2. unsigned intにキャスト:

    ptr = (SomeType*)((unsigned int)ptr) + offset);
    
  3. _size_t にキャスト:

    ptr = (SomeType*)((size_t)ptr) + offset);
    
  4. size_tptrdiff_tのサイズは常にポインターのサイズと一致します。このため、ポインターの格納とポインター演算のために、これらの型を大きな配列のインデックスとして使用する必要があります。」 - CodeProjectのsize_tおよびptrdiff_t について

    ptr = (SomeType*)((size_t)ptr + (ptrdiff_t)offset);
    
  5. または前と同じですが、 intptr_tsize_t の代わりに使用します。

    ptr = (SomeType*)((intptr_t)ptr + (ptrdiff_t)offset);
    
  6. offsetはすでに符号付き整数であり、 intptr_tintptr_t ではないため、size_tにのみキャストします。

    ptr = (SomeType*)((intptr_t)ptr) + offset);
    

そして、これらすべてのケースで、古いCスタイルのキャストを使用するのが安全ですか、それともstatic_castまたはreinterpret_castを使用する方が安全または移植性がありますか?

ポインタ値自体が符号なしまたは符号付きであると想定すべきですか?

私は次のようなものを使用します:

unsigned char* bytePtr = reinterpret_cast<unsigned char*>(ptr);
bytePtr += offset;
10
freddy.smith

_reinterpret_cast_(またはCスタイルのキャスト)を使用することは、型システムを回避することを意味し、移植性がなく安全ではありません。正しいかどうかは、アーキテクチャによって異なります。もしあなたがそれを(しなければならない)なら、あなたはそれをほのめかすあなたは何をするか知っているそしてあなたは基本的にそれ以降自分自身でいる。警告はこれで終わりです。

ポインタにnを追加するか、Tと入力すると、このポインタはnelementsタイプはTです。あなたが探しているのは、1つの要素が1バイトを意味するタイプです。

sizeofセクション5.3.3.1から:

Sizeof演算子は、そのオペランドのオブジェクト表現でバイト数を生成します。 [...] sizeof(char)sizeof(signed char)およびsizeof(unsigned char)1です。他の基本型(3.9.1)に適用されたsizeofの結果は実装定義です。

sizeof(int)などに関するステートメントはないことに注意してください。

byteの定義(セクション1.7.1):

C++メモリモデルの基本的なストレージユニットはバイトです。 1バイトは少なくとも、基本実行文字セット(2.3)のメンバーとUnicode UTF-8エンコード形式の8ビットコード単位を含むのに十分な大きさであり、連続するビットのシーケンスで構成されます。実装定義。 [...] C++プログラムで使用できるメモリは、連続するバイトの1つ以上のシーケンスで構成されています。 各バイトには一意のアドレスがあります。

したがって、sizeofがバイト数を返し、sizeof(char)が1の場合、charはC++に対して1バイトのサイズになります。したがって、char論理的に C++へのバイトですが、必ずしも事実上の標準の8ビットバイトではありません。 _char*_にnを追加すると、nバイト(C++メモリモデルの観点から)だけ離れたポインターが返されます。したがって、オブジェクトのポインタをバイト単位で操作するという危険なゲームをプレイしたい場合は、charバリアントの1つにキャストする必要があります。タイプにconstのような修飾子もある場合は、それらも「バイトタイプ」に転送する必要があります。

_    template <typename Dst, typename Src>
    struct adopt_const {
        using type = typename std::conditional< std::is_const<Src>::value,
            typename std::add_const<Dst>::type, Dst>::type;
    };

    template <typename Dst, typename Src>
    struct adopt_volatile {
        using type = typename std::conditional< std::is_volatile<Src>::value,
            typename std::add_volatile<Dst>::type, Dst>::type;
    };

    template <typename Dst, typename Src>
    struct adopt_cv {
        using type = typename adopt_const<
            typename adopt_volatile<Dst, Src>::type, Src>::type;
    };

    template <typename T>
    T*  add_offset(T* p, std::ptrdiff_t delta) noexcept {
        using byte_type = typename adopt_cv<unsigned char, T>::type;
        return reinterpret_cast<T*>(reinterpret_cast<byte_type*>(p) + delta);
    }
_

10
user2218982

NULLは特別であることに注意してください。オフセットを追加するのは危険です。
reinterpret_castconstまたはvolatile修飾子を削除できません。よりポータブルな方法は、Cスタイルのキャストです。
reinterpret_cast @ user2218982の回答のような特性を持つと、より安全に見えます。

template <typename T>
inline void addOffset( std::ptrdiff_t offset, T *&ptr ) { 
    if ( !ptr )
        return;
    ptr = (T*)( (unsigned char*)ptr + offset );
} 
1
where23