私はC++での作業に非常に慣れていないため、言語の複雑さと微妙さをすべて把握していません。
C++ 11の任意の型のポインターに任意のバイトオフセットを追加するための、最もポータブルで正確かつ安全な方法は何ですか?
SomeType* ptr;
int offset = 12345 /* bytes */;
ptr = ptr + offset; // <--
Stack OverflowとGoogleで多くの答えを見つけましたが、それらはすべて異なることを提案しています。私が遭遇したいくつかのバリアント:
_char *
にキャスト:
ptr = (SomeType*)(((char*)ptr) + offset);
unsigned int
にキャスト:
ptr = (SomeType*)((unsigned int)ptr) + offset);
_size_t
にキャスト:
ptr = (SomeType*)((size_t)ptr) + offset);
「size_t
とptrdiff_t
のサイズは常にポインターのサイズと一致します。このため、ポインターの格納とポインター演算のために、これらの型を大きな配列のインデックスとして使用する必要があります。」 - CodeProjectのsize_tおよびptrdiff_t について
ptr = (SomeType*)((size_t)ptr + (ptrdiff_t)offset);
または前と同じですが、 intptr_t
をsize_t
の代わりに使用します。
ptr = (SomeType*)((intptr_t)ptr + (ptrdiff_t)offset);
offset
はすでに符号付き整数であり、 intptr_t
はintptr_t
ではないため、size_t
にのみキャストします。
ptr = (SomeType*)((intptr_t)ptr) + offset);
そして、これらすべてのケースで、古いCスタイルのキャストを使用するのが安全ですか、それともstatic_cast
またはreinterpret_cast
を使用する方が安全または移植性がありますか?
ポインタ値自体が符号なしまたは符号付きであると想定すべきですか?
私は次のようなものを使用します:
unsigned char* bytePtr = reinterpret_cast<unsigned char*>(ptr);
bytePtr += offset;
_reinterpret_cast
_(またはCスタイルのキャスト)を使用することは、型システムを回避することを意味し、移植性がなく安全ではありません。正しいかどうかは、アーキテクチャによって異なります。もしあなたがそれを(しなければならない)なら、あなたはそれをほのめかすあなたは何をするか知っているそしてあなたは基本的にそれ以降自分自身でいる。警告はこれで終わりです。
ポインタにn
を追加するか、T
と入力すると、このポインタはn
elementsタイプは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);
}
_
NULL
は特別であることに注意してください。オフセットを追加するのは危険です。reinterpret_cast
はconst
または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 );
}