63

現在、バイナリ データを扱う必要がある場合があります。C++ では、一連のバイトを扱います。最初から、charビルディング ブロックでした。1を持つように定義されたsizeof、それはバイトです。また、すべてのライブラリ I/O 関数はcharデフォルトで使用します。すべては問題ありませんが、常に少しの懸念がありました。一部の人々を悩ませた少し奇妙な点です。1 バイトのビット数は処理系で定義されています。

そのため、C99 では、開発者が固定幅整数型を簡単に表現できるように、いくつかの typedef を導入することが決定されました。もちろん、可搬性を損なうことは絶対にしたくないので、オプションです。その中で、固定幅の 8 ビット符号なし整数型uint8_tとして C++11 に移行されstd::uint8_tた は、8 ビット バイトを本当に処理したい人にとって最適な選択でした。

std::uint8_t*そのため、開発者は新しいツールを採用し、8 ビットのバイト シーケンスをstd::vector<std::uint8_t>やその他の形式で受け入れることを明示的に示すライブラリの構築を開始しました。

しかし、おそらく非常に深く考えて、標準化委員会は の実装を要求しないことを決定したため、開発者がs をバイナリ データとしてstd::char_traits<std::uint8_t>簡単かつ移植可能にインスタンス化したりstd::basic_fstream<std::uint8_t>、簡単に読み取ったりすることを禁止しました。std::uint8_tあるいは、1 バイトのビット数を気にせず、満足している人もいます。

char*しかし、残念なことに、2 つの世界が衝突し、データを as として取得し、それを期待するライブラリに渡さなければならない場合がありますstd::uint8_t*。でもちょっと待って、char可変ビットじゃなくstd::uint8_tて8固定じゃないの?データが失われますか?

さて、これについて興味深い標準があります。はchar正確に 1 バイトを保持するように定義されており、バイトはメモリのアドレス指定可能な最小のチャンクであるため、ビット幅が のビット幅よりも小さい型は存在できませんchar。次に、UTF-8 コード単位を保持できるように定義されています。これにより、最小値である 8 ビットが得られます。これで、8 ビット幅である必要がある typedef と、少なくとも 8 ビット幅の型ができました。しかし、代替手段はありますか?はいunsigned charcharの署名は実装定義であることを思い出してください。他のタイプは?ありがたいことに、いいえ。他のすべての整数型には、8 ビット外の範囲が必要です。

最後に、std::uint8_tオプションです。つまり、この型を使用するライブラリは、定義されていないとコンパイルされません。しかし、それがコンパイルされるとどうなるでしょうか? これは、8 ビット バイトとCHAR_BIT == 8.

8 ビットのバイトがあり、 または のいずれかとして実装されているというこの知識をstd::uint8_t得たら、 からへ、またはその逆を行うことができると仮定できますか? ポータブルですか?charunsigned charreinterpret_castchar*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*しても安全ですか?std::uint8_t*char

説明付きの標準への参照をいただければ幸いです。

編集: ありがとう、ジェリー コフィン。標準 ([basic.lval]、§3.10/10) からの引用を追加します。

プログラムが、次の型以外の glvalue を介してオブジェクトの格納された値にアクセスしようとした場合、動作は未定義です。

...

— char または unsigned char 型。

EDIT2:わかりました、さらに深くなります。std::uint8_tの typedef であることは保証されませんunsigned char拡張符号なし整数型として実装でき、拡張符号なし整数型は §3.10/10 に含まれていません。今何?

4

2 に答える 2

33

わかりました、本当に衒学的になりましょう。thisthis、およびthisを読んだ後、私は両方の標準の背後にある意図を理解していると確信しています。

そのため、toを実行してreinterpret_castから結果のポインターを逆参照することは安全移植性があり、 [basic.lval]によって明示的に許可されています。std::uint8_t*char*

ただし、toを実行して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.");

このアサートを配置すると、未定義の動作が発生するプラットフォームでコードがコンパイルされなくなります。

2番:

std::memcpy(uint8buffer, charbuffer, size);

Cppreferenceは、std::memcpyオブジェクトに配列としてアクセスするunsigned charため、安全移植可能であると述べています。

繰り返しになりますが、100% 標準に準拠した方法で、結果のポインターを移植可能かつ安全操作reinterpret_castできるようにするには、次の条件が満たされている必要があります。char*std::uint8_t*

  • CHAR_BIT == 8.
  • std::uint8_tが定義されています。
  • std::uint8_tcharまたはとして実装されunsigned charます。

実際には、上記の条件は 99% のプラットフォームで真であり、最初の 2 つの条件が真で 3 番目の条件が偽であるプラットフォームはおそらく存在しません。

于 2013-04-28T10:06:53.573 に答える
20

存在する場合uint8_t、本質的に唯一の選択肢は、それが typedef であることですunsigned char(またはchar、たまたま署名されていない場合)。(ビットフィールドを除いて) より少ないストレージを表すことはcharできず、8 ビットほど小さくできる他の型はbool. 次に小さい通常の整数型は ashortで、少なくとも 16 ビットである必要があります。

そのため、uint8_t存在する場合、実際には 2 つの可能性しかありません。 にキャストunsigned charするかunsigned char、 にキャストsigned charunsigned charます。

前者は恒等変換なので、明らかに安全です。後者は、§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 charand uint8_t(存在する場合) は、ビット レベルで同じように表現する必要があります。

この 2 つの違いを確認するには、ビット レベルでは 2 つのビットの表現が同じでなければならないにもかかわらず、一部の特定のビットを一方と見なした場合と他方と見なした場合とでは異なる結果が生成されると断言する必要があります。

より直接的に言えば、2 つの結果の違いは、ビットを同じように解釈するという直接的な要件にもかかわらず、ビットを異なる方法で解釈する必要があります。

純粋に理論的なレベルでも、これを達成するのは難しいようです。実用的なレベルに近づくと、明らかにばかげています。

于 2013-04-28T06:06:29.057 に答える