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 は次のように述べています。
signed
char には、 、plain、の 3 種類が指定されていますunsigned
。プレーンchar
は、以前の慣行と同様に、実装に応じて、署名付きまたは未署名として表すことができます。この型signed char
は、プレーン char を unsigned として実装するシステムで 1 バイトの符号付き整数型を利用できるようにするために導入されました。対称性の理由から、キーワードsigned
は他の整数型の型名の一部として使用できます。
さらにさかのぼると、1975 年の初期バージョンのC リファレンス マニュアルには次のように書かれています。
char
オブジェクトはどこでも使用できint
ます。すべての場合において、 は結果の整数の上位 8 ビットを介してその符号を伝搬することによってchar
に変換されます。int
これは、文字と整数の両方に使用される 2 の補数表現と一致しています。(ただし、他の実装では符号伝搬機能はなくなります。)
この説明は、後のドキュメントで見られるものよりも実装固有のものですが、署名されてchar
いるか署名されていない可能性があることを認めています。「符号伝搬が消える」「他の実装」では、char
オブジェクトを に昇格int
させると、8 ビット表現がゼロ拡張され、本質的にそれを 8 ビットの符号なし量として扱います。(言語にはまだsigned
orunsigned
キーワードがありませんでした。)
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 です。char
0xff
CHAR_BIT==8
-1
0xff == c
int
0xff == -1
255 == -1
注意すべきもう 1 つの重要な点は、unsigned char
、signed char
、および (プレーン)char
が 3 つの異なる型であるということです。 または のいずれかとchar
同じ表現を持ちます。それがどれであるかは実装定義です。(一方、とは同じ型の 2 つの名前です。は別個の型です。(それを除いて、軽薄さを増すために、プレーンとして宣言されたビット フィールドが符号付きか符号なしかは実装定義です。)) unsigned char
signed char
signed int
int
unsigned int
int
はい、それはすべて少し混乱しています。今日 C がゼロから設計されていたら、別の方法で定義されていたと確信しています。しかし、C 言語の各リビジョンは、既存のコードを (過度に) 破壊することを避けなければなりませんでした。
char
最初は文字を格納するためのものなので、署名付きか未署名かは重要ではありません。本当に重要なのは、char
効率的に計算を実行する方法です。したがって、システムに応じて、コンパイラは最も適切なものを選択します
ARMv4 より前の ARM では、ハーフワードと符号付きバイトをロードするためのネイティブ サポートがありませんでした。符号付きバイトをロードするには、LDRB を実行してから値を符号拡張する必要がありました (LSL をアップしてから ASR をダウンに戻します)。これは面倒なので、char はデフォルトで unsigned になっています。
実際、多くの ARM コンパイラは依然としてunsigned char
デフォルトで使用しています。これは、最新の ARM ISA で符号拡張を使用してバイトをロードできたとしても、その命令はゼロ拡張バージョンよりも柔軟性が低いためです。
また、最近のほとんどのコンパイラでは、デフォルト設定を使用する代わりに char の符号を変更することもできます