なぜC文字リテラルが文字ではなく整数なのですか?


103

、C ++でsizeof('a') == sizeof(char) == 1。これは'a'文字リテラルでありsizeof(char) == 1、標準で定義されているため、直感的に理解できます。

ただし、Cではsizeof('a') == sizeof(int)。つまり、C文字リテラルは実際には整数であるように見えます。なぜ誰か知っていますか?私はこのCの癖について多くの言及を見つけることができますが、なぜそれが存在するのかについての説明はありません。


sizeofはバイトのサイズを返すだけでしょうか?charとintのサイズは同じではありませんか?
Josh Smeaton、

1
これはおそらくコンパイラ(およびアーキテクチャ)に依存しています。何を使っているか言っても大丈夫ですか?標準(少なくとも89年まで)は非常に緩やかでした。
dmckee ---元モデレーターの子猫、

2
番号。charは、いつものsizeof 1バイト大きい(「」)== 1は常に、(++ c)において、intはできますが、理論的にはsizeof 1のこと、それは、少なくとも16ビット、持つバイトを必要とする非常に低いと: )これはsizeof( '')=はsizeof(int)がある!非常に多くの実装では、C ++で可能性が高い
ヨハネス・シャウブ- litb

2
... Cでは常に間違っていますが
Johannes Schaub-litb 2009年

22
'a'はCのint-期間です。Cが最初に到着しました-Cがルールを作成しました。C ++はルールを変更しました。C ++ルールの方が理にかなっていると主張することはできますが、Cルールを変更すると害が大きくなるため、C標準委員会は賢明にこれに触れていません。
ジョナサンレフラー、

回答:


36

同じ主題に関する議論

「より具体的には、インテグラルプロモーションです。K&R Cでは、最初にintに昇格しないと文字値を使用することは事実上(?)不可能でした。そのため、そもそも文字定数をintにすることでそのステップが排除されました。 「abcd」などの定数、または多くはintに収まります。」


複数の文字の定数は、1台のマシンのコンパイラ間でも移植できません(GCCはプラットフォーム間で一貫性があるようです)。参照:stackoverflow.com/questions/328215
ジョナサンレフラー、

8
私は次のことに注意します。a)この引用は無帰属です。引用は単に「問題について議論している過去のスレッドに投稿されたこの意見に同意しませんか?」...およびb)変数はintではないので、それは滑稽charです。したがって、文字定数を1にすることは特殊なケースです。また、文字値を昇格せずに簡単に使用できますc1 = c2;。OTOH c1 = 'x'は下方変換です。最も重要なのはsizeof(char) != sizeof('x')、これは深刻な言語のボッチです。マルチバイト文字定数については、それが理由ですが、時代遅れです。
ジムバルター、2011年

27

元の質問は「なぜですか」です。

その理由は、既存のコードとの後方互換性を維持しようとする一方で、リテラル文字の定義が進化および変更されたためです。

初期のCの暗黒時代には、タイプはまったくありませんでした。私がCでのプログラミングを最初に学んだ時点で、型は導入されていましたが、関数には、引数の型が何であるかを呼び出し元に伝えるプロトタイプがありませんでした。代わりに、パラメーターとして渡されるすべてがint(これにはすべてのポインターが含まれます)のサイズまたはdoubleのいずれかになるように標準化されました。

つまり、関数を記述しているときに、doubleでないすべてのパラメーターは、どのように宣言したかに関係なく、intとしてスタックに格納され、コンパイラーはこれを処理するコードを関数に配置しました。

これにより、状況に多少の矛盾が生じたため、K&Rが有名な本を書いたとき、関数リテラルだけでなく、文字リテラルは常に任意の式でintに昇格するという規則を適用しました。

ANSI委員会が最初にCを標準化したとき、彼らはこのルールを変更して、文字リテラルが単にintになるようにしました。

C ++の設計時には、すべての関数に完全なプロトタイプが必要でした(これは、Cでは必須ではありませんが、優れた方法として広く受け入れられています)。このため、文字リテラルをcharに格納できるようになりました。C ++でのこの利点は、charパラメータを持つ関数とintパラメータを持つ関数のシグネチャが異なることです。この利点はCでは当てはまりません。

これが彼らが違う理由です。進化...


2
「なぜ?」に実際に回答してくれた私からの+1。しかし、私は最後のステートメントに同意しません-「C ++でのこの利点は、charパラメータを持つ関数とintパラメータを持つ関数のシグネチャが異なることです」-C ++では、2つの関数が同じサイズで異なる署名。例:void f(unsigned char)Vs void f(signed char)
Peter K

3
@PeterK Johnの方が優れているかもしれませんが、彼の言うことは本質的に正確です。C ++での変更の動機は、作成する場合f('a')、おそらくf(char)、その呼び出しに対してではなく、オーバーロードの解決を選択することf(int)でした。との相対的なサイズはintcharあなたが言うように関係ありません。
zwol 2017

21

Cの文字リテラルがint型である具体的な理由はわかりません。しかし、C ++では、そのようにしないほうがよい理由があります。このことを考慮:

void print(int);
void print(char);

print('a');

printを呼び出すと、charを使用する2番目のバージョンが選択されると予想されます。文字リテラルをintにすると、それは不可能になります。C ++では、複数の文字を持つリテラルの型は引き続きintですが、それらの値は実装で定義されています。したがって、'ab'type intがあり'a'ますが、typeがありcharます。


はい、「C ++の設計と進化」によると、C ++がルールを変更した主な理由は、オーバーロードされた入出力ルーチンでした。
Max Lybbert、2009年

5
マックス、そうだよ。私は互換性セクションで標準を調べました:)
Johannes Schaub-litb 09年

18

私の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です。


7
面白くて+1。多くの場合、sizeof( "a")とsizeof( "")はchar *であり、4(または8)を与える必要があると考えられています。しかし実際には、それらはその時点ではchar []です(sizeof(char [11])は11を示します)。初心者のためのわな。
paxdiablo 2009年

3
文字リテラルはintに昇格されず、すでにintです。オブジェクトがsizeof演算子のオペランドである場合は、昇格は行われません。あったとしても、これはsizeofの目的に反することになります。
クリスヤング

@クリス・ヤング:ええ。小切手。ありがとう。
dmckee ---元モデレーターの子猫、

8

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を参照


5

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;
}

0は有効な文字ではないと思います。
gbjbaanb 2009年

3
@gbjbaanb:もちろんです。ヌル文字です。それについて考えてください。ファイルにゼロバイトを含めることは許可されるべきではないと思いますか?
Pダディ

1
ウィキペディアを読む-「EOFの実際の値は、システムに依存する負の数値であり、通常は-1です。これは、有効な文字コードと等しくないことが保証されています。」
マルクス2009年

2
Malxが言うように-EOFはchar型ではなく-int型です。getchar()とそのフレンドはintを返します。これは、競合なしにEOFと同様に任意のcharを保持できます。これは、文字型がintである必要はありません。
マイケルバー

2
EOF == -1はCの文字定数のかなり後に来たので、これは答えではなく、関連性さえありません。
ジムBalter

5

その理由はわかりません(C文字リテラルはint型です)が、Stroustrupがそれについて言わざるを得なかったものです(Design and Evolution 11.2.1-Fine-Grain Resolutionから)。

Cでは、'a'is などの文字リテラルのタイプint。驚いたことに、C ++で'a'型を指定charしても、互換性の問題は発生しません。病理学的な例を除いて、sizeof('a')CとC ++の両方で表現できるすべての構成体は同じ結果になります。

したがって、ほとんどの場合、問題は発生しません。


面白い!「賢く」C.からこの癖を削除しないことを決定した方法C標準化委員会について言ったものを他の人にちょっと矛盾する
j_random_hacker

2

これの歴史的な理由は、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進数
でした

1

これは、「統合プロモーション」と呼ばれる正しい動作です。他の場合にも発生する可能性があります(私が正しく覚えていれば、主にバイナリ演算子です)。

編集:念のため、エキスパートCプログラミング:Deep Secretsのコピーを確認しました。charリテラルがint型で始まっていないことを確認しました。最初はchar型ですが、で使用すると、intにプロモートされます。以下は本から引用されています:

文字リテラルはint型を持ち、char型からの昇格のルールに従うことでそこに到達します。これは、39ページのK&R 1で簡単に説明しすぎています。

式内のすべてのcharはintに変換されます。...式内のすべてのfloatがdoubleに変換されます。...関数の引数は式であるため、引数が関数に渡されるときに型変換も行われます。特に、charとshortはintになり、floatはdoubleになります。


他のコメントが信じられる場合、式 'a' int型で始まります。sizeof()内では型の昇格は行われません。'a'の型がintであるということは、Cの奇妙なことです。
j_random_hacker 2009年

2
char型のリテラルはありません int型を持っています。ANSI / ISO 99標準では、それらを「整数文字定数」と呼び(wchar_t型の「ワイド文字定数」と区別するため)、特に「整数文字定数の型はintです」と述べています。
マイケル・バー、

私が意味したのは、それがint型で始まるのではなく、charからintに変換されるということでした(回答は編集済み)。もちろん、変換は常に行われるため、これはおそらくコンパイラの作成者以外には関係ありません。
PolyThinker 2009年

3
番号!ANSI / ISO 99 C規格読んだ場合、Cでは式 'a' int型で始まります。関数void f(int)と変数char cがある場合、f(c)整数昇格実行しますが、f( 'a')は 'a'の型がすでに intであるため機能しません。奇妙だが真実。
j_random_hacker 2009年

2
「念のために」-「文字リテラルの型はintです」というステートメントを実際に読むことで、より確実になります。「私はそれがサイレント変更の1つであったとしか想定できません」-あなたは誤って想定します。Cの文字リテラルは常にint型でした。
ジムバルター、2011年

0

わかりませんが、そのように実装する方が簡単だったと思うので、それほど重要ではありませんでした。型がどの関数が呼び出されるかを決定できるようになったのは、C ++が修正するまではありませんでした。


0

私は本当にこれを知りませんでした。プロトタイプが存在する前は、それを関数の引数として使用すると、intより狭いものはintに変換されていました。それは説明の一部かもしれません。


1
別の貧弱な「答え」。charto intを自動的に変換すると、文字定数をintにする必要がまったくなくなります。関連するのは、言語が文字定数をchar変数とは異なる方法で(異なる型を与えることによって)処理することであり、必要なのはその違いの説明です。
ジムバルター、2011年

以下にご説明いただきありがとうございます。回答の中で説明をより詳しく説明することをお勧めします。回答は、それが属する場所にあり、賛成票を投じ、訪問者が簡単に見ることができます。また、私はここで良い答えがあるとは決して言いませんでした。したがって、あなたの価値判断は役に立ちません。
Blaisorblade、2011年

0

これは言語仕様に正接するだけですが、ハードウェアでは通常、CPUには1つのレジスタサイズ(たとえば、32ビット)しかないため、実際にcharで(加算、減算、または比較によって)機能する場合は常に、レジスタにロードされるときに暗黙的にintに変換されます。コンパイラーは、各演算の後に数値を適切にマスキングおよびシフトするので、たとえば2を(unsigned char)254に追加した場合、256ではなく0に折り返されますが、シリコン内では実際にはintです。メモリに保存するまで。

とにかく言語が8ビットのリテラル型を指定している可能性があるため、これは一種の学術的なポイントですが、この場合、言語の仕様はたまたまCPUが実際に行っていることをより厳密に反映しています。

(x86 wonksは、たとえば、短い幅のレジスタを1つのステップで追加するネイティブのaddh op があることに注意するかもしれませんが、RISCコアの内部では、これは2つのステップに変換されます。 PowerPC)


1
さらに別の間違った答え。ここでの問題は、文字リテラルとchar変数の型が異なる理由です。ハードウェアを反映する自動昇格は関係ありません。char変数は自動的に昇格されるため、文字リテラルがタイプにならない理由はないため、実際には関連性がありませんchar。本当の理由はマルチバイトリテラルであり、現在は廃止されています。
ジムバルター、2011年

@Jim Balterマルチバイトリテラルは時代遅れではありません。マルチバイトのUnicodeおよびUTF文字があります。
Crashworks

@Crashworks マルチバイト文字列リテラルではなく、マルチバイト文字リテラルについて話しています。注意を払うようにしてください。
ジムバルター、2011年

4
Chrashworksは文字を書きました。ワイド文字リテラル(たとえばL'à ')はより多くのバイトを必要としますが、マルチバイト文字リテラルとは呼ばれないことを書いておく必要があります。傲慢さが下がれば、自分自身をより正確にすることができます。
Blaisorblade、2011年

@Blaisorbladeワイド文字リテラルはここでは関係ありません-私が書いたものとは何の関係もありません。私は正確でした、そしてあなたは理解に欠けており、私を訂正しようとするあなたの偽の試みは傲慢です。
ジムバルター、2011年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.