2010-12-14 25 views
4

ヘッダーファイルの宣言は必須ですか?このコード:ヘッダファイルの宣言は必須ですか?

main() 
{ 
    int i=100; 

    printf("%d\n",i); 
} 

が動作しているようですが、私が手出力がさえstdio.hヘッダファイルを使用せずに100です。これはどのように可能ですか?

+0

[理由#include はprintf()を使用する必要はありません?](http://stackoverflow.com/questions/336814/why-include-stdio-h-is-not-required-to- – vaxquis

答えて

9

どのようにこれは可能ですか?要するに:運の3つの部分。

これは、宣言されていない関数についていくつかのコンパイラが仮定を行うために可能です。具体的には、パラメータはintとし、戻り値の型もintとします。 intは多くの場合、char*(アーキテクチャによって異なる)と同じサイズであるため、適切なサイズのパラメータがスタックにプッシュされるため、intと文字列を渡すことができます。 printfが宣言されていなかったので、あなたの例では

は、それが2つのintのパラメータを取ると仮定した、とあなたは、呼び出しの面で「互換性」であるchar*intを可決しました。だから、コンパイラは肩をすくめて、正しいと思われるコードを生成しました。 (実際には宣言されていない関数について警告していたはずです)。

最初の幸運なことは、コンパイラの前提が実際の関数と互換性があったことです。

printfがC標準ライブラリの一部であるため、リンカ段階でコンパイラ/リンカはこれを自動的にリンク段階に含めます。 printfシンボルが実際にC stdlibにあったので、リンカがシンボルを解決し、すべてがうまくいっていました。リンクは第2の幸運でした。標準ライブラリ以外の場所でもライブラリがリンクされている必要があります。

最後に、実行時に3番目の運が表示されます。コンパイラは盲目的な仮定をしましたが、シンボルはデフォルトでリンクされていました。しかし、実行時にアプリケーションをクラッシュさせるような方法でデータを簡単に渡すことができました。幸いにも、パラメータが一致し、正しいことが起こりました。これは必ずしも当てはまるとは限りません.64ビットシステムでは、おそらく上記がうまくいかないでしょう。

元の質問に答えるには、ヘッダーファイルを含めることが本当に不可欠です。なぜなら、うまくいけば、それは盲目的な幸運なのですから!

+1

素晴らしい説明! – anatolyg

14

には、ヘッダーファイルを含めるのにが含まれていません。その目的は、コンパイラにstdioに関するすべての情報を知らせることですが、コンパイラがスマート(または遅延)の場合は決して必要ありません。それはに入るために良い習慣だから

あなたそれを含める必要があります - あなたがいない場合は、コンパイラを使用すると、のように、ルールを破っているかどうかを知るためには実際の方法がない:

int main (void) { 
    puts (7);  // should be a string. 
    return 0; 
} 

問題なくコンパイルされますが、実行中は正しくダンプします。

qq.c:3: warning: passing argument 1 of ‘puts’ makes pointer 
       from integer without a cast 

まともなコンパイラが見えるようになっているものprintfを知ることなどgccとして、これについて警告を表示可能性があります

#include <stdio.h> 
int main (void) { 
    puts (7); 
    return 0; 
} 

のようなものをあなたに警告コンパイラになります。それを変更しますでもヘッダなし:

qq.c:7: warning: incompatible implicit declaration of 
       built-in function ‘printf’ 
+0

paxdiablo @ gccコンパイラ自体を使用しています....私はUbuntu 10.04を使用していますLTS – Manu

0

paxidiabloとは言いませんが、これは関数と変数にのみ当てはまりますが、ヘッダーファイルにいくつかの型やマクロ(#define)がある場合は、必要に応じてヘッダファイルをインクルードする必要がありますリンクは、前処理中にすなわち起こるか

0

をコンパイルする前に、Cコンパイラが宣言されていない関数呼び出し(あなたのケースでのprintf())見たとき、それはそれは

 

int printf(...) 

を持っていることを前提としているので、これは可能です

シグネチャを返し、すべての引数をint型にキャストして呼び出すよう試みます。 "int"型と "void *"型はしばしば同じサイズを持つので、ほとんどの場合動作します。しかし、そのような行動に頼るのは賢明ではありません。関数の引数の型の

+0

すべての引数を 'int'にキャストしようとはしません。より複雑な引数の昇格スキーム(charとshort-> int、任意の* - > void *、float - > double AFAIR)を使用し、呼び出しを固定arg関数これらの引数で宣言します。そして、それはvararg '(...)'として扱われません。 – Vovanium

+0

キャスト修正に同意します。 varargの説明については、 を参照してください。http://stackoverflow.com/questions/336814/why-include-stdio-h-is-not-required-to-use-printf – imaximchuk

0

C supprots 3種類:

  1. 既知の固定引数:foo(int x, double y):あなたは引数を持つ関数を宣言するとき、これはあります。
  2. 不明固定引数:(foo(void)と混同しない:それは引数なしで最初の形式です)foo():あなたは空の括弧でそれを宣言するとき、これは、またはは全くにそれを宣言しません。
  3. 可変引数:これは、省略記号:foo(int x, ...)で宣言したときです。

標準機能が動作していると、機能定義(フォーム1または3)はフォーム2(同じ呼び出し規約を使用)と互換性があります。多くの古い標準。関数の宣言がなく、すべてが形式2になっていた初期のCの形をしているので、ライブラリ関数はそうである(designedされているように)。他の関数は、引数がこのフォームの引数昇格規則で宣言されています。しかし、そうでない人もいます。

しかし、フォーム2では、コンパイラはプロトタイプで引数をチェックすることができず、実際に渡された引数で呼び出し規約を判断する必要があるため、同じ型の引数を渡す必要があります。 (両方の形態1及び2)固定引数関数のMC68000機最初の2つの整数の引数に例えば

、レジスタD0及びD1A0A1の最初の2つのポインタが渡され、他のすべてがスタックに通します。 ptrcountD1で、sizeD0で、A0中とA1stream(とD0に結果を返す):だから、たとえば機能fwrite(const void * ptr, size_t size, size_t count, FILE * stream);はとして引数を取得します。 stdio.hを含めると、何にでも渡されます。

stdio.hを含めないと、別のことが起こります。 fwrite(data, sizeof(*data), 5, myfile)でfwriteを呼び出すと、コンパイラはargrumentsを調べ、その関数がfwrite(*, int, int, *)と呼ばれることを確認します。だからそれは何ですか?最初のポインタはA0で、最初のintはD0であり、2番目のintはD1であり、2番目のポインタはA1であるため、必要なものです。

しかし、countがdouble型のfwrite(data, sizeof(*data), 5.0, myfile)と呼び出そうとすると、コンパイラはcountを整数ではないのでスタックに渡そうとします。しかし機能要求はD1にあります。たわごとは起こる:D1は、countではなく、いくつかのゴミを含むので、さらなる動作は予測できません。しかし、stdio.hで定義されているプロトタイプを使用するよりも、すべてが問題ありません。コンパイラは自動的にこの引数をintに変換し、必要に応じて渡します。これは、抽象的な例ではないので、浮動小数点数を含む計算の結果としてdoubleになる可能性があります。

もう1つの例は、printf(char *fmt, ...)のような可変引数関数(形式3)です。そのためには、呼び出し規約では、最後の名前付き引数(ここではfmt)がその型のスタックを通して渡される必要があります。だから、printf("%d", 10)と呼ぶと、ポインタに"%d"と数字10が置かれ、必要に応じて関数が呼び出されます。

しかし、あなたはprintfは、可変引数関数であり、printf("%d", 10)は、ポインタ型とint型の固定引数で機能するように呼びかけていると仮定することを知ることができませんstdio.h comilerが含まれていないとき。したがって、MC68000はA0へのポインタとD0へのポインタをスタックの代わりに置き、結果は再び予測できません。

以前はスタック上に引数があったことがあり、時にはそこを読んで正しい結果を得ましたが、今度は...もう一度失敗します。もう一つの幸運は、宣言されていない関数がvararg(そして何らかの形で両方の形式と互換性のある呼び出しを行う)である場合、コンパイラが注意を払うことです。あるいは、すべての形式のすべての引数があなたのマシン上のスタックを通過するだけなので、固定された未知のvararg形式は、まったく同じように呼び出されます。

だからあなたは幸運を感じても動作しません。不明な固定引数形式は、古いコードとの互換性のためだけにあり、使用することを強くお勧めします。

注:C++はこれをまったく許可しません。は、既知の引数で関数を宣言する必要があります。

関連する問題