、C ++でsizeof('a') == sizeof(char) == 1
。これは'a'
文字リテラルでありsizeof(char) == 1
、標準で定義されているため、直感的に理解できます。
ただし、Cではsizeof('a') == sizeof(int)
。つまり、C文字リテラルは実際には整数であるように見えます。なぜ誰か知っていますか?私はこのCの癖について多くの言及を見つけることができますが、なぜそれが存在するのかについての説明はありません。
、C ++でsizeof('a') == sizeof(char) == 1
。これは'a'
文字リテラルでありsizeof(char) == 1
、標準で定義されているため、直感的に理解できます。
ただし、Cではsizeof('a') == sizeof(int)
。つまり、C文字リテラルは実際には整数であるように見えます。なぜ誰か知っていますか?私はこのCの癖について多くの言及を見つけることができますが、なぜそれが存在するのかについての説明はありません。
回答:
同じ主題に関する議論
「より具体的には、インテグラルプロモーションです。K&R Cでは、最初にintに昇格しないと文字値を使用することは事実上(?)不可能でした。そのため、そもそも文字定数をintにすることでそのステップが排除されました。 「abcd」などの定数、または多くはintに収まります。」
char
です。したがって、文字定数を1にすることは特殊なケースです。また、文字値を昇格せずに簡単に使用できますc1 = c2;
。OTOH c1 = 'x'
は下方変換です。最も重要なのはsizeof(char) != sizeof('x')
、これは深刻な言語のボッチです。マルチバイト文字定数については、それが理由ですが、時代遅れです。
元の質問は「なぜですか」です。
その理由は、既存のコードとの後方互換性を維持しようとする一方で、リテラル文字の定義が進化および変更されたためです。
初期のCの暗黒時代には、タイプはまったくありませんでした。私がCでのプログラミングを最初に学んだ時点で、型は導入されていましたが、関数には、引数の型が何であるかを呼び出し元に伝えるプロトタイプがありませんでした。代わりに、パラメーターとして渡されるすべてがint(これにはすべてのポインターが含まれます)のサイズまたはdoubleのいずれかになるように標準化されました。
つまり、関数を記述しているときに、doubleでないすべてのパラメーターは、どのように宣言したかに関係なく、intとしてスタックに格納され、コンパイラーはこれを処理するコードを関数に配置しました。
これにより、状況に多少の矛盾が生じたため、K&Rが有名な本を書いたとき、関数リテラルだけでなく、文字リテラルは常に任意の式でintに昇格するという規則を適用しました。
ANSI委員会が最初にCを標準化したとき、彼らはこのルールを変更して、文字リテラルが単にintになるようにしました。
C ++の設計時には、すべての関数に完全なプロトタイプが必要でした(これは、Cでは必須ではありませんが、優れた方法として広く受け入れられています)。このため、文字リテラルをcharに格納できるようになりました。C ++でのこの利点は、charパラメータを持つ関数とintパラメータを持つ関数のシグネチャが異なることです。この利点はCでは当てはまりません。
これが彼らが違う理由です。進化...
void f(unsigned char)
Vs void f(signed char)
。
f('a')
、おそらくf(char)
、その呼び出しに対してではなく、オーバーロードの解決を選択することf(int)
でした。との相対的なサイズはint
、char
あなたが言うように関係ありません。
Cの文字リテラルがint型である具体的な理由はわかりません。しかし、C ++では、そのようにしないほうがよい理由があります。このことを考慮:
void print(int);
void print(char);
print('a');
printを呼び出すと、charを使用する2番目のバージョンが選択されると予想されます。文字リテラルをintにすると、それは不可能になります。C ++では、複数の文字を持つリテラルの型は引き続きintですが、それらの値は実装で定義されています。したがって、'ab'
type int
があり'a'
ますが、typeがありchar
ます。
私のMacBookでgccを使用して、私は試してみます:
#include <stdio.h>
#define test(A) do{printf(#A":\t%i\n",sizeof(A));}while(0)
int main(void){
test('a');
test("a");
test("");
test(char);
test(short);
test(int);
test(long);
test((char)0x0);
test((short)0x0);
test((int)0x0);
test((long)0x0);
return 0;
};
実行すると次のようになります。
'a': 4
"a": 2
"": 1
char: 1
short: 2
int: 4
long: 4
(char)0x0: 1
(short)0x0: 2
(int)0x0: 4
(long)0x0: 4
これは、疑わしいように文字が8ビットであることを示唆していますが、文字リテラルはintです。
Cが作成されていた当時、PDP-11のMACRO-11アセンブリ言語には次のものが含まれていました。
MOV #'A, R0 // 8-bit character encoding for 'A' into 16 bit register
この種のことはアセンブリ言語では一般的です-下位8ビットは文字コードを保持し、他のビットは0にクリアされます。PDP-11には、
MOV #"AB, R0 // 16-bit character encoding for 'A' (low byte) and 'B'
これは、2つの文字を16ビットレジスタの下位バイトと上位バイトにロードする便利な方法を提供しました。次に、それらを別の場所に書き込んで、テキストデータまたは画面メモリを更新します。
そのため、文字を登録サイズに昇格させるという考えは、ごく普通で望ましいものです。しかし、ハードコードされたオペコードの一部としてではなく、メインメモリ内のどこかから「A」をレジスタに入れる必要があるとしましょう。
address: value
20: 'X'
21: 'A'
22: 'A'
23: 'X'
24: 0
25: 'A'
26: 'A'
27: 0
28: 'A'
このメインメモリからレジスタに「A」だけを読み取りたい場合、どれを読み取りますか?
一部のCPUは、16ビット値の16ビットレジスタへの読み取りのみを直接サポートしている可能性があります。つまり、20または22での読み取りは、「X」からのビットをクリアする必要があり、CPUのエンディアンに依存します。下位バイトにシフトする必要があります。
一部のCPUでは、メモリ境界での読み取りが必要になる場合があります。つまり、関係する最小のアドレスは、データサイズの倍数でなければなりません。アドレス24と25から読み取ることはできますが、27と28からは読み取れない場合があります。
したがって、「A」をレジスターに入れるコードを生成するコンパイラーは、少し余分なメモリーを浪費し、エンディアンに応じて値を0 'A'または 'A' 0としてエンコードすることを好むかもしれません。つまり、奇数のメモリアドレスではありません)。
私の推測では、CはこのレベルのCPU中心の動作を単純に引き継ぎ、メモリのレジスタサイズを占める文字定数を考え、「高レベルアセンブラ」としてのCの一般的な評価を展開していると思います。
(http://www.dmv.net/dec/pdf/macro.pdfの 6-25ページの6.3.3を参照)
K&Rを読み、EOFに到達するまで一度に文字を読み取るコードスニペットを見たのを覚えています。すべての文字はファイル/入力ストリームに存在する有効な文字であるため、EOFを文字値にすることはできません。コードが行ったのは、読み取った文字をintに入れ、次にEOFをテストし、そうでない場合はcharに変換することでした。
これはあなたの質問に正確に答えていないと思いますが、EOFリテラルがそうであった場合、残りの文字リテラルがsizeof(int)であることは理にかなっています。
int r;
char buffer[1024], *p; // don't use in production - buffer overflow likely
p = buffer;
while ((r = getc(file)) != EOF)
{
*(p++) = (char) r;
}
その理由はわかりません(C文字リテラルはint型です)が、Stroustrupがそれについて言わざるを得なかったものです(Design and Evolution 11.2.1-Fine-Grain Resolutionから)。
Cでは、
'a'
is などの文字リテラルのタイプint
。驚いたことに、C ++で'a'
型を指定char
しても、互換性の問題は発生しません。病理学的な例を除いて、sizeof('a')
CとC ++の両方で表現できるすべての構成体は同じ結果になります。
したがって、ほとんどの場合、問題は発生しません。
これの歴史的な理由は、Cおよびその前身であるBが、8ビットASCIIをサポートし、レジスターでの演算しか実行できない、さまざまなワードサイズのDEC PDPミニコンピューターのさまざまなモデルで開発されたことです。(ただし、PDP-11ではありませんが、これは後日発表されます。)Cの初期のバージョンint
は、マシンのネイティブワードサイズであると定義されており、関数に渡したり、関数から渡したりするために、int
必要な値よりも小さい値に拡張する必要がありint
ます。 、またはビット単位の論理式または算術式で使用されます。これは、基盤となるハードウェアが機能するためです。
また、整数の昇格ルールで、より小さいデータ型int
はに昇格されるといわれていint
ます。Cの実装では、同様の歴史的理由により、2の補数ではなく1の補数演算を使用することもできます。8進文字エスケープと8進定数が16進と比較して一流の市民である理由は、それらの初期のDECミニコンピューターが3バイトのチャンクに分割できるが4バイトのニブルには分割できないワードサイズを持っているためです。
char
正確に3桁の8進数
これは、「統合プロモーション」と呼ばれる正しい動作です。他の場合にも発生する可能性があります(私が正しく覚えていれば、主にバイナリ演算子です)。
編集:念のため、エキスパートCプログラミング:Deep Secretsのコピーを確認しました。charリテラルがint型で始まっていないことを確認しました。最初はchar型ですが、式で使用すると、intにプロモートされます。以下は本から引用されています:
文字リテラルはint型を持ち、char型からの昇格のルールに従うことでそこに到達します。これは、39ページのK&R 1で簡単に説明しすぎています。
式内のすべてのcharはintに変換されます。...式内のすべてのfloatがdoubleに変換されます。...関数の引数は式であるため、引数が関数に渡されるときに型変換も行われます。特に、charとshortはintになり、floatはdoubleになります。
わかりませんが、そのように実装する方が簡単だったと思うので、それほど重要ではありませんでした。型がどの関数が呼び出されるかを決定できるようになったのは、C ++が修正するまではありませんでした。
私は本当にこれを知りませんでした。プロトタイプが存在する前は、それを関数の引数として使用すると、intより狭いものはintに変換されていました。それは説明の一部かもしれません。
char
to int
を自動的に変換すると、文字定数をintにする必要がまったくなくなります。関連するのは、言語が文字定数をchar
変数とは異なる方法で(異なる型を与えることによって)処理することであり、必要なのはその違いの説明です。
これは言語仕様に正接するだけですが、ハードウェアでは通常、CPUには1つのレジスタサイズ(たとえば、32ビット)しかないため、実際にcharで(加算、減算、または比較によって)機能する場合は常に、レジスタにロードされるときに暗黙的にintに変換されます。コンパイラーは、各演算の後に数値を適切にマスキングおよびシフトするので、たとえば2を(unsigned char)254に追加した場合、256ではなく0に折り返されますが、シリコン内では実際にはintです。メモリに保存するまで。
とにかく言語が8ビットのリテラル型を指定している可能性があるため、これは一種の学術的なポイントですが、この場合、言語の仕様はたまたまCPUが実際に行っていることをより厳密に反映しています。
(x86 wonksは、たとえば、短い幅のレジスタを1つのステップで追加するネイティブのaddh op があることに注意するかもしれませんが、RISCコアの内部では、これは2つのステップに変換されます。 PowerPC)
char
変数の型が異なる理由です。ハードウェアを反映する自動昇格は関係ありません。char
変数は自動的に昇格されるため、文字リテラルがタイプにならない理由はないため、実際には関連性がありませんchar
。本当の理由はマルチバイトリテラルであり、現在は廃止されています。