int main()
{
char c = 0xff;
bool b = 0xff == c;
// Under most C/C++ compilers' default options, b is FALSE!!!
}
C または C++ 標準は char を符号付きまたは符号なしとして指定しておらず、実装定義です。
上記のコードのような危険な誤用を避けるために、C/C++ 標準が char を符号付きまたは符号なしとして明示的に定義しないのはなぜですか?
int main()
{
char c = 0xff;
bool b = 0xff == c;
// Under most C/C++ compilers' default options, b is FALSE!!!
}
C または C++ 標準は char を符号付きまたは符号なしとして指定しておらず、実装定義です。
上記のコードのような危険な誤用を避けるために、C/C++ 標準が char を符号付きまたは符号なしとして明示的に定義しないのはなぜですか?
主に歴史的な理由。
型の式charは、ほとんどのコンテキストで昇格さintれます (多くの CPU には 8 ビットの算術演算がないため)。一部のシステムでは、符号拡張がこれを行う最も効率的な方法であり、単純なchar符号付きにすることを主張しています。
一方、EBCDIC 文字セットには、上位ビット セットを持つ基本文字 (つまり、値が 128 以上の文字) があります。EBCDIC プラットフォームでは、char署名されていないことがほとんどです。
ANSI C Rationale (1989 規格用) では、この件について多くのことを述べていません。セクション 3.1.2.5 は次のように述べています。
signedchar には、 、plain、の 3 種類が指定されていますunsigned。プレーンcharは、以前の慣行と同様に、実装に応じて、署名付きまたは未署名として表すことができます。この型signed charは、プレーン char を unsigned として実装するシステムで 1 バイトの符号付き整数型を利用できるようにするために導入されました。対称性の理由から、キーワードsignedは他の整数型の型名の一部として使用できます。
さらにさかのぼると、1975 年の初期バージョンのC リファレンス マニュアルには次のように書かれています。
charオブジェクトはどこでも使用できintます。すべての場合において、 は結果の整数の上位 8 ビットを介してその符号を伝搬することによってcharに変換されます。intこれは、文字と整数の両方に使用される 2 の補数表現と一致しています。(ただし、他の実装では符号伝搬機能はなくなります。)
この説明は、後のドキュメントで見られるものよりも実装固有のものですが、署名されてcharいるか署名されていない可能性があることを認めています。「符号伝搬が消える」「他の実装」では、charオブジェクトを に昇格intさせると、8 ビット表現がゼロ拡張され、本質的にそれを 8 ビットの符号なし量として扱います。(言語にはまだsignedorunsignedキーワードがありませんでした。)
C の直前の言語は B と呼ばれる言語でした。B は型のない言語であったため、char符号付きか符号なしかという問題は当てはまりませんでした。C の初期の歴史の詳細については、故 Dennis Ritchie のホームページ(現在はここに移動) を参照してください。
コードで何が起こっているかについて (最新の C ルールを適用):
char c = 0xff;
bool b = 0xff == c;
プレーンcharが署名されていない場合、 の初期化により にc設定され、2 行目の(char)0xffと比較されます。0xffしかし、plaincharが署名されている場合、 0xff(type の式) は--intに変換されますが、 CHAR_MAX を超えているため ( と仮定して)、結果は実装定義です。ほとんどの実装では、結果は. 比較では、両方のオペランドが に変換され、 、 またはと同等になります。これはもちろん false です。char0xffCHAR_BIT==8-10xff == cint0xff == -1255 == -1
注意すべきもう 1 つの重要な点は、unsigned char、signed char、および (プレーン)charが 3 つの異なる型であるということです。 または のいずれかとchar同じ表現を持ちます。それがどれであるかは実装定義です。(一方、とは同じ型の 2 つの名前です。は別個の型です。(それを除いて、軽薄さを増すために、プレーンとして宣言されたビット フィールドが符号付きか符号なしかは実装定義です。)) unsigned char signed charsigned intintunsigned intint
はい、それはすべて少し混乱しています。今日 C がゼロから設計されていたら、別の方法で定義されていたと確信しています。しかし、C 言語の各リビジョンは、既存のコードを (過度に) 破壊することを避けなければなりませんでした。
char最初は文字を格納するためのものなので、署名付きか未署名かは重要ではありません。本当に重要なのは、char効率的に計算を実行する方法です。したがって、システムに応じて、コンパイラは最も適切なものを選択します
ARMv4 より前の ARM では、ハーフワードと符号付きバイトをロードするためのネイティブ サポートがありませんでした。符号付きバイトをロードするには、LDRB を実行してから値を符号拡張する必要がありました (LSL をアップしてから ASR をダウンに戻します)。これは面倒なので、char はデフォルトで unsigned になっています。
実際、多くの ARM コンパイラは依然としてunsigned charデフォルトで使用しています。これは、最新の ARM ISA で符号拡張を使用してバイトをロードできたとしても、その命令はゼロ拡張バージョンよりも柔軟性が低いためです。
また、最近のほとんどのコンパイラでは、デフォルト設定を使用する代わりに char の符号を変更することもできます