Cで変数のデータ型を指定する必要があるのはなぜですか


19

通常、Cでは、変数宣言でデータの種類をコンピューターに伝える必要があります。たとえば、次のプログラムでは、2つの浮動小数点数XとYの合計を出力します。

#include<stdio.h>
main()
{
  float X=5.2;
  float Y=5.1;
  float Z;
  Z=Y+X;
  printf("%f",Z);

}

変数Xの型をコンパイラーに伝える必要がありました。

  • コンパイラXはそれ自身で型を決定できませんか?

はい、これを行うとできます:

#define X 5.2

コンパイラーに次のようなタイプを伝えることなく、プログラムを作成できるようになりXました。

#include<stdio.h>
#define X 5.2
main()
{
  float Y=5.1;
  float Z;
  Z=Y+X;
  printf("%f",Z);

}  

したがって、C言語には何らかの種類の機能があり、それを使用してデータのタイプを独自に判別できることがわかります。私の場合、それXはfloat型であると判断されました。

  • main()で何かを宣言するとき、なぜデータのタイプに言及しなければならないのですか?なぜコンパイラは、自身の中に変数のデータ型を判断することはできませんmain()、それはでませんよう#define

14
実際、これらの2つのプログラムは、わずかに異なる出力を与える可能性があるという点で、同等ではありません!5.2あるdouble最初のプログラムは、二重リテラルを丸めて、float5.1背面の第二ラウンドの二重表現しながら、浮動小数点数としてそれらを追加し、その後、精度doubleとに追加double使用して値5.2 doubleに加えを、その後にその計算結果を丸めるfloat精度。丸めはさまざまな場所で行われるため、結果が変化する可能性があります。これは、他の点では同一のプログラムの動作に影響を与える変数のタイプのほんの一例です。

12
を実行する#define X 5.2X、変数ではなく定数になり5.2ますX。したがって、プリプロセッサで指定した場所に文字通り置き換えられます。再割り当てできませんX
Scriptin 14

16
ちょうどメモ:これは祝福と呪いです。一方では、コンパイラーが本当にあなたのためにできたときに、いくつかの文字を入力する必要があります(C ++ autoは実際にあなたが望むことをします)。一方、コードが何をしているのか知っていて、実際に他の何かを入力したと思う場合、このような静的型付けは、大きな問題になる前にエラーを早期にキャッチします。すべての言語は、静的型付け、型推論、動的型付けのバランスを取ります。いくつかのタスクでは、余分な入力は実際に価値があります。他の人にとっては、無駄です。
コートアンモン-復帰モニカ14

Ocamlおよび/またはHaskellを学んでください。
バジルスタリンケビッチ

回答:


46

変数宣言を#defines と比較していますが、これは正しくありません。を使用して#define、ソースコードの識別子とスニペット間のマッピングを作成します。Cプリプロセッサは、指定されたスニペットでその識別子の出現を文字通り置換します。書き込み

#define FOO 40 + 2
int foos = FOO + FOO * FOO;

コンパイラにとって、書くことと同じことになる

int foos = 40 + 2 + 40 + 2 * 40 + 2;

自動コピー&ペーストと考えてください。

また、通常の変数は再割り当てできますが、で作成されたマクロは再割り当て#defineできません(再割り当てはできます#define)。FOO = 7「rvalues」に割り当てることができないため、式はコンパイラエラーになり40 + 2 = 7ます。これは不正です。

それでは、なぜ型が必要なのでしょうか?一部の言語は明らかに型を取り除きますが、これはスクリプト言語では特に一般的です。ただし、通常、変数には固定型はないが値にはある「動的型付け」と呼ばれるものがあります。これははるかに柔軟性がありますが、パフォーマンスも低下します。Cはパフォーマンスが好きなので、変数の非常にシンプルで効率的な概念があります。

「スタック」と呼ばれる一連のメモリがあります。各ローカル変数は、スタック上の領域に対応しています。ここで問題は、この領域が何バイト必要かということです。Cでは、各タイプのサイズは明確に定義されており、を介してクエリできますsizeof(type)。コンパイラーは、各変数のタイプを認識して、スタック上の適切な量のスペースを確保できるようにする必要があります。

で作成された定数に#define型注釈が必要ないのはなぜですか?スタックには保存されません。代わりに、#defineコピーと貼り付けよりも保守しやすい方法で、ソースコードの再利用可能なスニペットを作成します。"foo"またはなどのソースコード内のリテラルは、42.87コンパイラによって特別な命令としてインラインで格納されるか、結果のバイナリの別のデータセクションに格納されます。

ただし、リテラルには型があります。文字列リテラルはchar *です。42ですが、intより短いタイプにも使用できます(縮小変換)。42.8になりますdouble。あなたはリテラルを持っており、それは別の種類を持っているしたい場合(例えば作るために、または、あなたはサフィックスを使用することができます) -文字をリテラルた後、その変更方法コンパイラ扱いリテラルいます。私たちの場合、またはと言うかもしれません。42.8float42unsigned long int42.8f42ul

一部の言語にはCのように静的型付けがありますが、型注釈はオプションです。例は、ML、Haskell、Scala、C#、C ++ 11、およびGoです。それはどのように機能しますか?魔法?いいえ、これは「型推論」と呼ばれます。C#およびGoでは、コンパイラは割り当ての右側を見て、その型を推測します。右側がなどのリテラルである場合、これはかなり簡単です42ul。それから、変数の型がどうあるべきかは明らかです。他の言語には、変数の使用方法を考慮したより複雑なアルゴリズムもあります。例えば、あなたがしなければx/2、そのx文字列にすることはできませんが、いくつかの数値型を持っている必要があります。


説明してくれてありがとう。私が理解しているのは、変数のタイプ(ローカルまたはグローバル)を宣言するとき、実際にコンパイラーに、スタック内のその変数のためにどれだけのスペースを確保すべきかを伝えていることです。一方で、#defineバイナリコードに直接変換される定数がありますが、それは長くてもかまいませんが、それはそのままメモリに格納されます。
user106313 14

2
@ user31782-まったく違います。変数を宣言すると、型はコンパイラに変数のプロパティを伝えます。これらのプロパティの1つはサイズです。その他のプロパティには、値の表現方法や、それらの値に対して実行できる操作が含まれます。
ピートベッカー14

@PeteBeckerそれでは、コンパイラはこれらの他のプロパティをどのようにして知るの#define X 5.2でしょうか?
user106313 14

1
間違った型を渡すことで、printf未定義の動作を呼び出したためです。私のマシンでは、スニペットは毎回異なる値を出力しますが、Ideoneではゼロを出力するとクラッシュします。
マッテオイタリア14

4
@ user31782 - 「私はすべてのデータ型に対して任意の操作を行うことができますように思え」号がX*Yあれば有効ではありませんXし、Yポインタであるが、彼らしている場合、それは大丈夫ですintの。*Xがの場合Xは無効ですがint、ポインタの場合は問題ありません。
ピートベッカー14

4

2番目の例のXはフロートではありません。これはマクロと呼ばれ、ソースで定義されているマクロ値「X」を値に置き換えます。#defineに関する読みやすい記事はこちらです。

提供されたコードの場合、コンパイルの前にプリプロセッサがコードを変更します

Z=Y+X;

Z=Y+5.2;

それがコンパイルされます。

つまり、これらの「値」を次のようなコードに置き換えることもできます

#define X sqrt(Y)

あるいは

#define X Y

3
可変長マクロではなく、単にマクロと呼ばれます。可変長マクロは、可変数の引数を取るマクロです#define FOO(...) { __VA_ARGS__ }
hvd 14

2
私の悪い、修正します:)
ジェームズスネル14

1

簡単な答えは、履歴/ハードウェアを表すため、Cには型が必要であるということです。

歴史:Cは1970年代初期に開発され、システムプログラミングの言語として意図されていました。コードは理想的には高速で、ハードウェアの機能を最大限に活用します。

コンパイル時に型を推測することは可能でしたが、すでに遅いコンパイル時間は増加していました(XKCDの「コンパイル」漫画を参照してください。これは、Cが公開されてから少なくとも10年間は​​「hello world」に適用されていました)。実行時に型を推論することは、システムプログラミングの目的に適合しませんでした。ランタイム推論には、追加のランタイムライブラリが必要です。Cは最初のPCよりもずっと前に登場しました。256個のRAMがありました。ギガバイトやメガバイトではなく、キロバイト。

あなたの例では、タイプを省略した場合

   X=5.2;
   Y=5.1;

   Z=Y+X;

そうすれば、コンパイラーはXとYが浮動小数点数であり、Zを同じにすることができたはずです。実際、最新のコンパイラーでは、XとYは不要であり、Zを10.3に設定するだけです。

計算が関数内に埋め込まれていると仮定します。関数の作成者は、ハードウェアに関する知識、または解決される問題を使用する場合があります。

doubleはfloatよりも適切ですか?より多くのメモリを使用し、速度は低下しますが、結果の精度は高くなります。

floatからintへの変換にはコストがかかりますが、小数は重要ではないため、関数の戻り値はint(またはlong)になる可能性があります。

戻り値は、float + floatがオーバーフローしないことを保証する二重にすることもできます。

これらの質問はすべて、今日書かれているコードの大部分にとっては無意味に思えますが、Cが作成されたときには非常に重要でした。


1
型宣言は、プログラマが明示的に宣言するか、推測するために、コンパイラに依存するか選択することができ、オプションで行われていなかった理由は、これは例えば説明していない
ブヨ

1
実際、@ gnatはありません。私はテキストを微調整しましたが、そのようにすることには意味がありませんでした。ドメインCは、実際には17を1バイト、2バイト、4バイト、文字列、または単語内の5ビットとして保存することを決定するために設計されました。
itj

0

Cには型推論がありません(コンパイラが変数の型を推測したときに呼び出されるものです)。1970年代初期に開発されました

多くの新しい言語には、タイプ(ruby、javascript、pythonなど)を指定せずに変数を使用できるシステムがあります。


12
あなたが言及した言語(Ruby、JS、Python)はどれも、言語機能として型推論を持ちませんが、実装は効率を高めるためにそれを使用するかもしれません。代わりに、値には型がありますが変数や他の式にはない動的型付けを使用します
アモン14

2
JSはしません許可あなたがする省略形を-それはちょうどあなたが全くそれを宣言することはできませんです。値は変数ではなくタイプ(例:trueis boolean)を持つ動的型付けを使用します(例:var x任意のタイプの値を含むことができます)。また、質問からのような単純なケースの型推論は、Cがリリースされる10年前におそらく知られていました。
scriptin 14

2
それはステートメントを偽りにしない(何かを強制するためにあなたもそれを許可しなければならない)。既存の型推論は、Cの型システムがその歴史的文脈の結果であるという事実を変更しません(具体的に述べられた哲学的推論または技術的制限とは対照的に)
Tristan Burnside 14

2
ML(Cとほとんど同じ)が型推論を持っていることを考えると、「古い」というのは良い説明ではありません。Cが使用および開発されたコンテキスト(コンパイラーに非常に小さなフットプリントを要求する小さなマシン)は、より可能性が高いようです。型推論を備えた言語の例だけではなく、動的型付け言語に言及する理由がわかりません-Haskell、ML、一体C#にあります-もうあいまいな機能はほとんどありません。
Voo 14

2
@BradS。変数名の最初の文字があるためFortranは良い例ではないですあなたが使用しない限り、型宣言implicit none、その場合には、あなたがしなければならない型を宣言します。
dmckee 14
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.