web-dev-qa-db-ja.com

C / C ++バイナリデータにunsigned charを使用する理由

文字エンコードまたはバイナリバッファで動作する一部のライブラリのように、_unsigned char_を使用してバイナリデータを保持する必要は本当にありますか?私の質問を理解するために、以下のコードを見てください-

_char c[5], d[5];
c[0] = 0xF0;
c[1] = 0xA4;
c[2] = 0xAD;
c[3] = 0xA2;
c[4] = '\0';

printf("%s\n", c);
memcpy(d, c, 5);
printf("%s\n", d);
_

_printf's_は両方とも_????_を正しく出力します。ここで_f0 a4 ad a2_は、UnicodeコードポイントU+24B62 (????)の16進数のエンコードです。

memcpyでも、charが保持するビットを正しくコピーしました。

_unsigned char_の代わりに_plain char_を使用することを推奨できる理由は何ですか?

他の関連する質問では、_unsigned char_が強調表示されます。これは、C仕様によってパディングがないことが保証されている唯一の(バイト/最小)データ型であるためです。しかし、上記の例が示したように、出力はパディング自体の影響を受けないようです。

VC++ Express 2010とMinGWを使用して上記をコンパイルしました。 VCは警告を発した

_warning C4309: '=' : truncation of constant value_

出力はそれを反映していないようです。

追伸これは、 バイトバッファを符号付きまたは符号なしcharバッファにする必要がありますか? の重複の可能性があるとマークできますが、私の意図は異なります。私はなぜcharでうまく動作しているように見えるものを_unsigned char_とタイプする必要があるのか​​と尋ねています。

更新:N3337から引用するには、

_Section 3.9 Types_

2簡単にコピー可能なタイプTのオブジェクト(ベースクラスサブオブジェクト以外)について、オブジェクトがタイプTの有効な値を保持しているかどうかにかかわらず、オブジェクトを構成する基本バイト(1.7)をcharの配列にコピーできますまたはunsigned char。 charまたはunsigned charの配列の内容がオブジェクトにコピーされた場合、オブジェクトは元の値を保持します。

上記の事実と、私の元の例がcharがデフォルトの_signed char_であるIntelマシン上にあったことを考えると、charよりも_unsigned char_を優先すべきかどうかはまだわかりません。

他に何か?

48
nightlytrails

Cでは、unsigned charデータ型は、次の3つのプロパティをすべて同時に持つ唯一のデータ型です

  • すべてのストレージビットがデータの値に寄与するパディングビットがない
  • その型の値から始まるビット演算は、その型に変換されたときに、オーバーフロー、トラップ表現、または未定義の動作を引き起こすことはありません
  • 「エイリアスルール」に違反することなく他のデータ型をエイリアスすることができます。つまり、異なる型のポインターを介した同じデータへのアクセスは、すべての変更を確認することが保証されます。

これらが探している「バイナリ」データ型のプロパティである場合、unsigned charを確実に使用する必要があります。

2番目のプロパティには、unsignedである型が必要です。これらのすべての変換は、モジュロ算術で定義されます。ここでは、ほとんどのアーキテクチャのUCHAR_MAX+1256を法としてモジュロします。これにより、より広い値からunsigned charへのすべての変換は、最下位バイトへの切り捨てに対応します。

通常、他の2つの文字タイプは同じようには機能しません。 signed charはとにかく署名されるので、それに適合しない値の変換は明確に定義されていません。 charは署名済みまたは署名なしに固定されていませんが、コードが移植される特定のプラットフォームでは、署名されていない場合でも署名される可能性があります。

83
Jens Gustedt

個々のバイトの内容を比較すると、ほとんどの問題が発生します。

char c[5];
c[0] = 0xff;
/*blah blah*/
if (c[0] == 0xff)
{
    printf("good\n");
}
else
{
    printf("bad\n");
}

コンパイラによっては、c [0]が-1に符号拡張されるため、「bad」を出力できますが、これは0xffとはまったく異なります。

13
Tom Tanner

プレーンchar型は問題があり、文字列以外には使用しないでください。 charの主な問題は、署名されているか署名されていないかがわからないことです。これは実装定義の動作です。これにより、charintなどと異なり、intは常に署名されていることが保証されます。

VCは警告を出しました...定数値の切り捨て

これは、char変数内にintリテラルを保存しようとしていることを示しています。これは、符号付きに関連している可能性があります。符号付き文字内に値> 0x7Fの整数を格納しようとすると、予期しないことが起こる可能性があります。正式には、これはCでの未定義の動作ですが、(符号付き)char内に格納された整数値として結果を印刷しようとすると、実際には奇妙な出力が得られます。

この特定のケースでは、警告は重要ではありません。

編集:

他の関連する質問では、C仕様によってパディングがないことが保証されている唯一の(バイト/最小)データ型であるため、unsigned charが強調表示されます。

理論的には、C11 6.2.6.2に従って、unsigned charおよびsigned charを除くすべての整数型に「パディングビット」を含めることが許可されています。

「符号なしchar以外の符号なし整数型の場合、オブジェクト表現のビットは、値ビットとパディングビットの2つのグループに分けられます(後者は必要ありません)。」

「符号付き整数型の場合、オブジェクト表現のビットは、値ビット、パディングビット、および符号ビットの3つのグループに分けられます。パディングビットは不要です。符号付きcharはパディングビットを持ちません。」

C標準は意図的に曖昧であいまいであり、これらの理論上のパディングビットを許可します。

  • 標準の8ビットのものとは異なるシンボルテーブルを使用できます。
  • これにより、実装定義の符号付きと、補数や「符号と大きさ」などの奇妙な符号付き整数形式が可能になります。
  • 整数は、割り当てられたすべてのビットを使用するとは限りません。

ただし、C標準以外の現実の世界では、以下が適用されます。

  • シンボルテーブルはほぼ確実に8ビット(UTF8またはASCII)です。奇妙な例外もいくつかありますが、8ビットより大きいシンボルテーブルを実装する場合、クリーンな実装では標準タイプwchar_tを使用します。
  • 署名は常に2の補数です。
  • 整数は常に割り当てられたすべてのビットを使用します。

そのため、C標準の理論的なシナリオを回避するためだけに、unsigned charまたはsigned charを使用する本当の理由はありません。

12
Lundin

バイトは通常、符号なし8ビット幅整数として意図されています。

現在、charは整数の符号を指定していません。一部のコンパイラではcharが署名され、他のコンパイラではunsignedになります。

あなたが書いたコードにビットシフト演算を追加すると、未定義の動作になります。追加された比較も予期しない結果になります。

char c[5], d[5];
c[0] = 0xF0;
c[1] = 0xA4;
c[2] = 0xAD;
c[3] = 0xA2;
c[4] = '\0';
c[0] >>= 1; // If char is signed, will the 7th bit go to 0 or stay the same?

bool isBiggerThan0 = c[0] > 0; // FALSE if char is signed!

printf("%s\n", c);
memcpy(d, c, 5);
printf("%s\n", d);

コンパイル中の警告について:charが署名されている場合、値0xf0を割り当てようとしていますが、これはsigned char(-128〜+127の範囲)で表すことができないため、符号付きの値にキャストされます(- 16)。

Charをunsignedとして宣言すると、警告が削除され、警告なしでクリーンビルドを作成することは常に有効です。

6
Paolo Brandoli

プレーンchar型の符号付きは実装定義であるため、実際に文字データ(プラットフォームの文字セットを使用する文字列-通常はASCII)を処理している場合を除き、通常は符号付きを指定することをお勧めしますsigned charまたはunsigned charを使用して明示的に。

バイナリデータの場合、特にビット単位の演算がデータに対して実行される場合(特にビットシフト、符号なしの型と符号付きの型で同じ動作をしない場合)は、おそらくunsigned charが最適です。

4

文字エンコードまたはバイナリバッファで動作する一部のライブラリのように、バイナリデータを保持するためにunsigned charを使用することが本当に必要ですか?

「本当に」必要ですか?番号。

それは非常に良い考えですが、これには多くの理由があります。

この例では、タイプセーフではないprintfを使用しています。つまり、printfは、データ型からではなく、フォーマット文字列からフォーマットキューを取得します。同様に簡単に試すことができます:

printf("%s\n", (void*)c);

...そして結果は同じだったでしょう。 c ++ iostreamで同じことをしようとすると、結果は異なります(cの符号付き度に応じて)。

プレーンな文字の代わりに符号なしの文字の使用を提唱する可能性のある推論は何ですか?

符号なしは、データの最上位ビット(符号なしcharの8番目のビット)が符号を表すことを指定します。明らかにそれを必要としないので、データが符号なしであることを指定する必要があります(「符号」ビットは他のビットの符号ではなくデータを表します)。

2
utnapistim

私はなぜcharでうまく機能しているように見えるものをunsigned charと入力する必要があるのですか?

標準の意味で「正しくない」ことを行う場合、未定義の動作に依存します。コンパイラーは、今日あなたが望む方法でそれを行うかもしれませんが、明日何をするのかわかりません。 GCCやVC++ 2012の機能がわからない。または、動作が外部要因やデバッグ/リリースコンパイルなどに依存している場合でも、標準の安全なパスを離れるとすぐに問題が発生する可能性があります。

2
Philipp

さて、「バイナリデータ」とは何ですか?これはビットの集まりであり、「バイナリデータ」と呼ばれるソフトウェアの特定の部分によって割り当てられた意味はありません。これらのビットのいずれかに特定の意味がないという考えを伝える最も近いプリミティブデータ型は何ですか?おもう unsigned char

2
chill