現在、私たちは皆、バイナリデータを処理する必要があります。 C++では、バイトのシーケンスを処理します。最初からchar
がビルディングブロックでした。 sizeof
が1になるように定義されています。これはバイトです。また、すべてのライブラリI/O関数は、デフォルトでchar
を使用します。すべてが良いですが、常に少し懸念があり、一部の人々を悩ませた少し奇妙なことがありました-バイトのビット数は実装定義です。
したがって、C99では、固定幅の整数型である開発者が自分自身を簡単に表現できるように、いくつかのtypedefを導入することが決定されました。もちろん、オプションです。移植性を損なうことは決してないからです。その中で、uint8_t
は、固定幅の8ビット符号なし整数型であるstd::uint8_t
としてC++ 11に移行され、8ビットバイトを実際に使用したい人にとって最適な選択肢でした。
そのため、開発者は新しいツールを採用し、std::uint8_t*
、std::vector<std::uint8_t>
などの8ビットバイトシーケンスを受け入れることを明示的に示すライブラリの構築を開始しました。
しかし、おそらく非常に深い考えで、標準化委員会はstd::char_traits<std::uint8_t>
の実装を必要としないことを決定したため、開発者がstd::basic_fstream<std::uint8_t>
を簡単かつ移植可能にインスタンス化し、std::uint8_t
sをバイナリとして簡単に読み取ることを禁止しましたデータ。あるいは、バイトのビット数を気にせず、満足している人もいます。
しかし、残念ながら、2つの世界が衝突し、場合によっては、データをchar*
として取得し、std::uint8_t*
を期待するライブラリに渡す必要があります。しかし、ちょっと待ってください、char
変数ビットではなく、std::uint8_t
は8に固定されていますか?データが失われますか?
まあ、これには興味深いStandardeseがあります。正確に1バイトを保持するように定義されたchar
は、メモリのアドレス可能な最小チャンクであるため、ビット幅がchar
よりも小さい型は存在できません。次に、UTF-8コード単位を保持できるように定義されています。これにより、最小-8ビットが得られます。これで、8ビット幅であることが必要なtypedefと少なくとも8ビット幅のタイプができました。しかし、代替案はありますか?はい、unsigned char
。 char
の符号は実装によって定義されることに注意してください。他のタイプ?ありがたいことに、違います。他のすべての整数型には、8ビットの範囲外の必須範囲があります。
最後に、std::uint8_t
はオプションです。つまり、このタイプを使用するライブラリは、定義されていないとコンパイルされません。しかし、それがコンパイルされたらどうでしょうか?これは、8ビットのバイトとCHAR_BIT == 8
を備えたプラットフォーム上にいることを意味します。
8ビットのバイトがあり、std::uint8_t
がchar
またはunsigned char
として実装されていることがわかったら、reinterpret_cast
をchar*
からstd::uint8_t*
へ、またはその逆?ポータブルですか?
これが私のスタンダードリーディングスキルの失敗です。安全に派生したポインタ([basic.stc.dynamic.safety]
)について読み、理解している限りでは、次のとおりです。
std::uint8_t* buffer = /* ... */ ;
char* buffer2 = reinterpret_cast<char*>(buffer);
std::uint8_t buffer3 = reinterpret_cast<std::uint8_t*>(buffer2);
buffer2
に触れなければ安全です。私が間違っていたら訂正してください。
したがって、次の前提条件を前提とします。
CHAR_BIT == 8
std::uint8_t
が定義されています。バイナリデータを処理していて、char
の符号の欠如の可能性は問題ではないと想定して、char*
とstd::uint8_t*
を前後にキャストすることは移植可能で安全ですか?
説明付きで規格を参照していただければ幸いです。
編集:ありがとう、ジェリー・コフィン。標準からの引用を追加します([basic.lval]、§3.10/ 10):
プログラムが次のタイプのいずれか以外のglvalueを介してオブジェクトの格納された値にアクセスしようとした場合の動作は未定義です。
...
— charまたはunsigned char型。
EDIT2:わかりました。 std::uint8_t
がunsigned char
のtypedefであるとは限りません。 拡張された符号なし整数型として実装でき、拡張された符号なし整数型は§3.10/ 10に含まれていません。今何?
さて、本当の知識を得ましょう。 this 、 this 、および this を読んだ後、両方の標準の背後にある意図を理解していることを確信しています。
したがって、reinterpret_cast
からstd::uint8_t*
にchar*
を実行し、結果のポインタを逆参照すると、safeおよびportableそして [basic.lval] によって明示的に許可されています。
ただし、reinterpret_cast
をchar*
からstd::uint8_t*
に変更し、結果のポインターを逆参照すると、厳密なエイリアス規則に違反し、になります。未定義の動作std::uint8_t
が拡張された符号なし整数型として実装されている場合。
ただし、最初に次の2つの回避策があります。
static_assert(std::is_same_v<std::uint8_t, char> ||
std::is_same_v<std::uint8_t, unsigned char>,
"This library requires std::uint8_t to be implemented as char or unsigned char.");
このアサートが設定されていると、コードは、未定義の動作が発生するプラットフォームではコンパイルされません。
第二:
std::memcpy(uint8buffer, charbuffer, size);
Cppreference は、std::memcpy
がunsigned char
の配列としてオブジェクトにアクセスするため、safeおよびportableであることを示します。
繰り返しますが、reinterpret_cast
とchar*
の間でstd::uint8_t*
を実行し、結果のポインタportablyとsafely100%標準に準拠した方法で、次の条件を満たしている必要があります。
CHAR_BIT == 8
。std::uint8_t
が定義されています。std::uint8_t
は、char
またはunsigned char
として実装されます。実際には、上記の条件はプラットフォームの99%に当てはまり、最初の2つの条件が当てはまるプラットフォームと、3番目の条件が当てはまるプラットフォームはおそらくないでしょう。
uint8_t
が存在する場合、基本的に唯一の選択肢は、それがunsigned char
(または、たまたま署名されていない場合はchar
)のtypedefであるということです。何も(ただしビットフィールドは)char
よりも少ないストレージを表すことができず、8ビットまで小さくできる唯一の他のタイプはbool
です。次に小さい通常の整数型はshort
であり、少なくとも16ビットでなければなりません。
そのため、uint8_t
がまったく存在する場合、実際に2つの可能性しかありません:unsigned char
をunsigned char
にキャストするか、signed char
をunsigned char
にキャストする。
前者はID変換なので、明らかに安全です。後者は、§3.10/ 10でcharまたはunsigned charのシーケンスとして他のタイプにアクセスするために与えられた「特別な規定」に該当するため、定義された動作も提供します。
これにはchar
とunsigned char
の両方が含まれるため、charのシーケンスとしてそれにアクセスするためのキャストも、定義された動作を提供します。
編集:Lucが拡張整数型について言及している限り、この場合にどうやってそれを適用して違いを得ることができるかはわかりません。 C++はuint8_t
などの定義についてC99標準を参照しているため、これ以降の引用はC99から引用されます。
§6.2.6.1/ 3は、unsigned char
がパディングビットなしの純粋なバイナリ表現を使用することを指定しています。埋め込みビットは、6.2.6.2/1でのみ許可されます。これにより、unsigned char
が明確に除外されます。ただし、このセクションでは、純粋なバイナリ表現について詳しく説明しています。したがって、unsigned char
およびuint8_t
(存在する場合)は、ビットレベルで同じように表す必要があります。
2つの違いを確認するには、特定のビットを一方として表示すると、他方として表示した場合とは異なる結果が得られると主張する必要があります。
より直接的に言えば、2つの結果の違いは、ビットを同じように解釈するという直接的な要件にもかかわらず、ビットを異なる方法で解釈することを要求します。
純粋に理論的なレベルでさえ、これを達成するのは難しいようです。実用レベルに近づくと、明らかにばかげています。