web-dev-qa-db-ja.com

なぜC文字リテラルはcharsではなくintですか?

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

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

101
Joseph Garvin

同じ主題 に関する議論

「特にインテグラルプロモーション。K&R Cでは、最初にintに昇格せずに文字値を使用することは事実上(?)不可能でした。 「abcd」などの定数または多くがintに収まります。」

36
Malx

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

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

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

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

このため、物事に多少の矛盾が生じたため、K&Rが有名な本を書いたとき、関数リテラルだけでなく、文字リテラルは常に式でintに昇格されるというルールを導入しました。

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

C++が設計されていたとき、すべての関数は完全なプロトタイプを持つ必要がありました(これはCではまだ必要ではありませんが、良い習慣として広く受け入れられています)。このため、文字リテラルをcharに格納できることが決定されました。 C++でのこの利点は、charパラメーターを持つ関数とintパラメーターを持つ関数の署名が異なることです。この利点はCでは当てはまりません。

これが異なる理由です。進化...

25
John Vincent

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

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

print('a');

Printを呼び出すと、charを使用する2番目のバージョンが選択されることが予想されます。文字リテラルをintにすると、それは不可能になります。 C++では、複数の文字を持つリテラルはint型を保持しますが、その値は実装で定義されています。そう、 'ab'のタイプはintですが、'a'のタイプは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です。

18
dmckee

Cが記述されていた頃、PDP-11のMACRO-11アセンブリ言語には次のものがありました。

MOV #'A, R0      // 8-bit character encoding for 'A' into 16 bit register

この種のことはアセンブリ言語では非常に一般的です-下位8ビットは文字コードを保持し、他のビットは0にクリアされます。

MOV #"AB, R0     // 16-bit character encoding for 'A' (low byte) and 'B'

これにより、16ビットレジスタの下位バイトと上位バイトに2つの文字をロードする便利な方法が提供されました。その後、それらを他の場所に書き込み、テキストデータまたは画面メモリを更新します。

そのため、文字を登録サイズに昇格させるという考えは非常に普通で望ましいものです。しかし、ハードコードされたオペコードの一部としてではなく、以下を含むメインメモリのどこかから「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での読み取りには、CPUのエンディアンに応じて「X」からのビットをクリアする必要があります。下位バイトにシフトする必要があります。

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

7
Tony Delroy

K&Rを読んで、EOFに達するまで一度に文字を読み取るコードスニペットを見たことを覚えています。すべての文字はファイル/入力ストリームに含まれる有効な文字であるため、これはEOFにはchar値を指定できないことを意味します。コードは、読み取った文字を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;
}
6
Kyle Cronin

私はその理由を見ていません(C charリテラルはint型です)が、Stroustrupがそれについて言わなければならないことがあります(Design and Evolution 11.2.1-Fine-Grain Resolutionから):

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

そのため、ほとんどの場合、問題は発生しません。

5
Michael Burr

これの歴史的な理由は、Cとその前身であるBが、さまざまなWordサイズのDEC PDPミニコンピューターのさまざまなモデルで開発され、8ビットASCII (ただし、PDP-11ではありません。後で登場しました。)Cの初期バージョンでは、intがマシンのネイティブWordサイズであり、intより小さい値が必要であると定義されていました。関数とやり取りするためにintに拡張したり、ビット単位の論理式や算術式で使用したりすること。

また、整数プロモーションルールでは、intよりも小さいデータ型はすべてintに昇格されると言われています。 Cの実装では、同様の歴史的な理由から、2の補数ではなく1の補数を使用することもできます。 8進文字のエスケープと8進定数が16進数と比較して一流の市民である理由は、同様に初期のDECミニコンピューターが4バイトのニブルではなく3バイトのチャンクに分割可能なWordサイズを持っていたためです。

1
Davislor

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

編集:念のため、Expert Cプログラミングのコピーをチェックしました:Deep Secrets、charリテラルがstart witha type int。最初はタイプcharですが、expressionで使用される場合、promotedto int。以下は本から引用されています。

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

式のすべての文字はintに変換されます...式のすべてのfloatはdoubleに変換されることに注意してください...関数の引数は式であるため、引数が関数に渡されるときにも型変換が行われます:in特に、charとshortはintになり、floatはdoubleになります。

1
PolyThinker

わかりませんが、そのように実装する方が簡単で、実際には問題ではなかったと思います。修正が必要なのは、どの関数が呼び出されるかを型が判別できるようになるのはC++でした。

0
Roland Rabien

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

0
Blaisorblade

これは言語仕様の正接にすぎませんが、ハードウェアでは通常、CPUには1つのレジスタサイズ(32ビット、たとえば)しかありません。したがって、実際にcharを処理するとき(加算、減算、または比較) intがレジスタにロードされるときに暗黙的にintに変換されます。コンパイラは、各操作の後に数値を適切にマスクし、シフトします。たとえば、(符号なし文字)254に2を追加すると、256の代わりに0に折り返されますが、シリコン内部では実際にはintですメモリに保存するまで。

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

(x86ウォンクはegが1ステップで短い幅のレジスタを追加するネイティブaddh opであることに気付くかもしれませんが、RISCコア内ではこれは2つのステップに変換されます:数字を追加してから符号を拡張し、 PowerPCのadd/extshペアのように)

0
Crashworks