17

言語が関数と型なし変数の暗黙の宣言を許可することが賢明なのはなぜですか?Cは古いと思いますが、宣言を省略してデフォルトint()(またはint変数の場合)にすることを許可することは、当時でさえ私にはそれほど正気ではないようです。

では、なぜ最初に導入されたのですか?本当に便利でしたか?それは実際に(まだ)使用されていますか?

注:最近のコンパイラーは(渡すフラグに応じて)警告を出し、この機能を抑制できることを理解しています。それは問題ではありません!


例:

int main() {
  static bar = 7; // defaults to "int bar"
  return foo(bar); // defaults to a "int foo()"
}

int foo(int i) {
  return i;
}
4

5 に答える 5

14

それはいつもの話です —ヒステリックなレーズン(別名「歴史的理由」)。

当初、C が実行されていた大型コンピューター (DEC PDP-11) は、データとコード用に 64 KiB (後にそれぞれ 64 KiB) を持っていました。コンパイラをどれだけ複雑にして実行できるかには限界がありました。実際、アセンブラーを使用する必要がなく、C などの高級言語を使用して O/S を作成できるという懐疑的な意見がありました。そのため、サイズの制約がありました。また、私たちが話しているのはずっと前、1970 年代の初めから半ばまでです。一般に、コンピューティングは現在ほど成熟した分野ではありませんでした (特にコンパイラーはあまり理解されていませんでした)。また、C の派生元の言語 (B および BCPL) には型がありませんでした。これらはすべて要因でした。

それ以来、言語は進化してきました (ありがたいことに)。コメントや反対票の回答で広く指摘されているように、厳密な C99 では、int変数の暗黙的な宣言と暗黙的な関数宣言の両方が廃止されました。ただし、ほとんどのコンパイラは依然として古い構文を認識しており、多かれ少なかれ警告を付けてその使用を許可し、後方互換性を維持しているため、古いソース コードは以前と同様にコンパイルおよび実行されます。C89 は、いぼ ( gets()) などすべてをそのままに、言語を大幅に標準化しました。これは、C89 標準を受け入れ可能にするために必要でした。

古い表記法を使用する古いコードがまだ残っています — 私はかなりの時間を古代のコードベース (最も古い部分については 1982 年頃) の作業に費やしています。しかし、数百万行のコードを含むコード ベースで 1 人ができることは限られています)。int変数の「暗黙的」をまだ持っているものはほとんどありません。関数が使用前に宣言されていない場所が多すぎます。また、関数の戻り値の型がまだ暗黙的に である場所がいくつかありintます。そのような混乱に対処する必要がない場合は、あなたの前に行った人に感謝してください.

于 2012-08-06T20:12:44.453 に答える
14

Dennis Ritchie の「C 言語の開発」を参照してください: http://cm.bell-labs.com/who/dmr/chist.html

例えば、

B の作成中に発生した広範な構文のバリエーションとは対照的に、BCPL のコア セマンティック コンテンツ (型構造と式評価規則) はそのまま残りました。どちらの言語も型がないか、固定長のビット パターンである「単語」または「セル」という単一のデータ型を持っています。これらの言語のメモリは、そのようなセルの線形配列で構成され、セルの内容の意味は適用される操作によって異なります。たとえば、+ 演算子は、マシンの整数加算命令を使用してそのオペランドを単純に追加します。他の算術演算も同様に、オペランドの実際の意味を認識しません。メモリは線形配列であるため、セルの値をこの配列のインデックスとして解釈することができ、BCPL はこの目的のために演算子を提供します。元の言語では rv と綴られ、後に ! と綴られましたが、B は単項 * を使用します。したがって、p が別のセルのインデックス (またはアドレス、またはポインター) を含むセルである場合、*p は、式の値または代入のターゲットとして、ポイント先のセルの内容を参照します。 .

この型なしは、作成者が異なる語長を持つマシンへの移植を開始するまで、C で持続しました。

この時期、特に 1977 年頃の言語の変更は、かなりの量のコードを新しい Interdata プラットフォームに移行する際に予見し観察した問題に対処するために、主に移植性と型の安全性を考慮することに重点が置かれていました。当時の C は、その型のない起源の強い兆候をまだ示していました。たとえば、ポインターは、初期の言語マニュアルまたは現存するコードの整数メモリ インデックスとほとんど区別されませんでした。文字ポインターと符号なし整数の算術特性の類似性により、それらを識別したいという誘惑に抵抗することが難しくなりました。符号なしの型は、ポインタ操作と混同することなく符号なしの算術演算を利用できるようにするために追加されました。同様に、初期の言語では整数とポインタの間の代入を容認していました。しかし、この慣習は落胆し始めました。型変換の表記 (Algol 68 の例では「キャスト」と呼ばれる) は、型変換をより明示的に指定するために発明されました。PL/I の例に惑わされて、初期の C は構造体ポインターをそれらが指す構造体にしっかりと結びつけておらず、プログラマーがポインターの型にほとんど関係なく pointer->member を書くことを許可していました。このような式は、ポインタによって指定されたメモリ領域への参照として無批判に取られましたが、メンバー名はオフセットと型のみを指定していました。また、プログラマーがポインターの型にほとんど関係なく、pointer->member を記述できるようにしました。このような式は、ポインタによって指定されたメモリ領域への参照として無批判に取られましたが、メンバー名はオフセットと型のみを指定していました。また、プログラマーがポインターの型にほとんど関係なく、pointer->member を記述できるようにしました。このような式は、ポインタによって指定されたメモリ領域への参照として無批判に取られましたが、メンバー名はオフセットと型のみを指定していました。

プログラミング言語は、プログラミングの実践が変化するにつれて進化します。多くのプログラマーがアセンブリ言語を書いたことがない現代の C および現代のプログラミング環境では、int とポインターが交換可能であるという考えは、ほとんど理解不能で正当化できないように思えるかもしれません。

于 2012-08-06T20:26:34.140 に答える
8

おそらく「なぜ」の最良の説明はここから来ます:

配列とポインターの関係と、宣言構文が式構文を模倣する方法です。それらはまた、最も頻繁に批判される機能の 1 つであり、初心者にとってつまずきのブロックとなることがよくあります。どちらの場合も、過去の事故や過ちが問題を悪化させています。これらの中で最も重要なのは、型のエラーに対する C コンパイラの耐性です。上記の歴史から明らかなように、C は型のない言語から進化しました。. 初期のユーザーや開発者に、独自のルールを持つまったく新しい言語として突然現れたわけではありません。代わりに、言語の開発に合わせて既存のプログラムを継続的に適応させ、既存のコード本体を考慮に入れる必要がありました。(後に、C を標準化する ANSI X3J11 委員会が同じ問題に直面することになります。)

システム プログラミング言語は必ずしも型を必要としません。フロートや整数、構造体、文字列ではなく、バイトや単語をいじっています。型システムは、最初から言語の一部ではなく、断片的に移植されました。C が主にシステム プログラミング言語から汎用プログラミング言語に移行したため、型の処理方法がより厳密になりました。しかし、パラダイムは行き来しますが、レガシー コードは永遠です。その Implicit に依存するコードはまだたくさんintあり、標準化委員会は機能しているものを壊すことに消極的です。そのため、それを取り除くのに30年近くかかりました。

于 2012-08-06T20:27:08.987 に答える
6

ずっと昔、K&R の ANSI 以前の時代には、関数は現在とはかなり異なって見えました。

add_numbers(x, y)
{
    return x + y;
}

int ansi_add_numbers(int x, int y); // modern, ANSI C

のような関数を呼び出す場合add_numbers、呼び出し規則に重要な違いがあります。関数が呼び出されると、すべての型が「昇格」されます。したがって、これを行う場合:

// no prototype for add_numbers
short x = 3;
short y = 5;
short z = add_numbers(x, y);

xに昇格されintyが に昇格されint、戻り値の型がデフォルトであると想定さintれます。同様に、a を渡すfloatと double に昇格されます。これらの規則により、適切な戻り値の型を取得し、適切な数と型の引数を渡す限り、プロトタイプは不要であることが保証されました。

プロトタイプの構文は異なることに注意してください:

// K&R style function
// number of parameters is UNKNOWN, but fixed
// return type is known (int is default)
add_numbers();

// ANSI style function
// number of parameters is known, types are fixed
// return type is known
int ansi_add_numbers(int x, int y);

昔の一般的な方法は、ほとんどの部分でヘッダー ファイルを使用せず、プロトタイプをコードに直接貼り付けることでした。

void *malloc();

char *buf = malloc(1024);
if (!buf) abort();

最近の C では、ヘッダー ファイルは必要悪として受け入れられていますが、最新の C の派生物 (Java、C# など) がヘッダー ファイルを削除したように、昔の人もヘッダー ファイルの使用をあまり好みませんでした。

型安全性

C以前の古い時代について私が理解していることから、静的型付けシステムが常に多くあるとは限りませんでした。intポインターを含め、すべてが でした。この古い言語では、関数プロトタイプの唯一のポイントは、アリティ エラーをキャッチすることでした。

したがって、最初に関数が言語に追加され、次に静的型システムが後で追加されたと仮定すると、この理論はプロトタイプがオプションである理由を説明します。この理論は、配列が関数の引数として使用されたときにポインターに崩壊する理由も説明しています。これは、このプロト C では、配列は、スタック上のスペースを指すように自動的に初期化されるポインターに過ぎなかったからです。たとえば、次のようなことが可能だった可能性があります。

function()
{
    auto x[7];
    x += 1;
}

引用

タイプレスについて:

両方の言語 [B と BCPL] は型がないか、固定長のビット パターンである「単語」または「セル」という単一のデータ型を持っています。

整数とポインタの等価性について:

したがって、pが別のセルのインデックス (またはアドレス、またはポインタ) を含むセル*pである場合は、式の値または代入のターゲットとして、ポイント先のセルの内容を参照します。

サイズの制約のためにプロトタイプが省略されたという理論の証拠:

開発中、彼はメモリの制限と格闘し続けました。言語を追加するたびにコンパイラが膨張し、ほとんど収まりませんでしたが、機能を利用して書き直すたびにサイズが小さくなりました。

于 2012-08-06T20:26:55.620 に答える
2

考えるためのいくつかの食べ物。(これは答えではありません。私たちは実際に答えを知っています — 下位互換性のために許可されています。)

そして、人々はなぜ 30 年もの間クリーンアップされていないのかを言う前に、COBOL コード ベースまたは f66 ライブラリーに目を向けるべきです!

gccデフォルトでは、警告は吐き出されません。

-Wall正しいことをgcc -std=c99吐き出すと

main.c:2: warning: type defaults to ‘int’ in declaration of ‘bar’
main.c:3: warning: implicit declaration of function ‘foo’

lint現代に組み込まれた機能性がgccその色を見せています。

興味深いことに、lintセキュリティで保護された の最新のクローンは、lintデフォルトsplintで 1 つの警告しか表示しません。

main.c:3:10: Unrecognized identifier: foo
  Identifier used in code has not been declared. (Use -unrecog to inhibit
  warning)

のように静的アナライザーも組み込まれているllvmC コンパイラーは、デフォルトで 2 つの警告を吐き出します。clanggcc

main.c:2:10: warning: type specifier missing, defaults to 'int' [-Wimplicit-int]
  static bar = 7; // defaults to "int bar"
  ~~~~~~ ^
main.c:3:10: warning: implicit declaration of function 'foo' is invalid in C99
      [-Wimplicit-function-declaration]
  return foo(bar); // defaults to a "int foo()"
         ^

人々は、80 年代のものには後方互換性は必要ないと考えていました。すべてのコードをクリーンアップまたは置換する必要があります。しかし、そうではないことがわかりました。多くの製品コードは、有史以前の非標準時代のままです。

編集:

私は投稿する前に他の回答を調べませんでした。ポスターの意図を誤解している可能性があります。しかし問題は、コードを手動でコンパイルし、toggle を使用してバイナリ パターンをメモリに格納することがあったということです。彼らは「型システム」を必要としませんでした。また、Richie と Thompson がこのようにポーズをとっている PDP マシンも同様です。

あごひげを見ないでください。マシンをブートストラップするために使用されたと聞いた「トグル」を見てください。

K&R

また、この論文で UNIX を起動する方法も調べてください。Unix 7版のマニュアルからです。

http://wolfram.schneider.org/bsd/7thEdManVol2/setup/setup.html

問題のポイントは、KB サイズのメモリを備えたマシンを管理するための多くのソフトウェア層を必要としなかったということです。Knuth の MIX には 4000 語があります。MIX コンピュータをプログラムするために、これらすべてのタイプが必要なわけではありません。このようなマシンでは、整数とポインターを喜んで比較できます。

なぜ彼らがこれをしたのかは自明だと思いました。そこで、後始末がどれだけ残っているかに注目した。

于 2012-08-06T20:33:57.907 に答える