ヘッダーファイルの宣言は必須ですか?このコード:ヘッダファイルの宣言は必須ですか?
main()
{
int i=100;
printf("%d\n",i);
}
が動作しているようですが、私が手出力がさえstdio.h
ヘッダファイルを使用せずに100です。これはどのように可能ですか?
ヘッダーファイルの宣言は必須ですか?このコード:ヘッダファイルの宣言は必須ですか?
main()
{
int i=100;
printf("%d\n",i);
}
が動作しているようですが、私が手出力がさえstdio.h
ヘッダファイルを使用せずに100です。これはどのように可能ですか?
どのようにこれは可能ですか?要するに:運の3つの部分。
これは、宣言されていない関数についていくつかのコンパイラが仮定を行うために可能です。具体的には、パラメータはint
とし、戻り値の型もint
とします。 int
は多くの場合、char*
(アーキテクチャによって異なる)と同じサイズであるため、適切なサイズのパラメータがスタックにプッシュされるため、int
と文字列を渡すことができます。 printf
が宣言されていなかったので、あなたの例では
は、それが2つのint
のパラメータを取ると仮定した、とあなたは、呼び出しの面で「互換性」であるchar*
とint
を可決しました。だから、コンパイラは肩をすくめて、正しいと思われるコードを生成しました。 (実際には宣言されていない関数について警告していたはずです)。
最初の幸運なことは、コンパイラの前提が実際の関数と互換性があったことです。
printf
がC標準ライブラリの一部であるため、リンカ段階でコンパイラ/リンカはこれを自動的にリンク段階に含めます。 printf
シンボルが実際にC stdlibにあったので、リンカがシンボルを解決し、すべてがうまくいっていました。リンクは第2の幸運でした。標準ライブラリ以外の場所でもライブラリがリンクされている必要があります。
最後に、実行時に3番目の運が表示されます。コンパイラは盲目的な仮定をしましたが、シンボルはデフォルトでリンクされていました。しかし、実行時にアプリケーションをクラッシュさせるような方法でデータを簡単に渡すことができました。幸いにも、パラメータが一致し、正しいことが起こりました。これは必ずしも当てはまるとは限りません.64ビットシステムでは、おそらく上記がうまくいかないでしょう。
元の質問に答えるには、ヘッダーファイルを含めることが本当に不可欠です。なぜなら、うまくいけば、それは盲目的な幸運なのですから!
素晴らしい説明! – anatolyg
には、ヘッダーファイルを含めるのにが含まれていません。その目的は、コンパイラに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’
paxdiablo @ gccコンパイラ自体を使用しています....私はUbuntu 10.04を使用していますLTS – Manu
paxidiabloとは言いませんが、これは関数と変数にのみ当てはまりますが、ヘッダーファイルにいくつかの型やマクロ(#define)がある場合は、必要に応じてヘッダファイルをインクルードする必要がありますリンクは、前処理中にすなわち起こるか
をコンパイルする前に、Cコンパイラが宣言されていない関数呼び出し(あなたのケースでのprintf())見たとき、それはそれは
int printf(...)
シグネチャを返し、すべての引数をint型にキャストして呼び出すよう試みます。 "int"型と "void *"型はしばしば同じサイズを持つので、ほとんどの場合動作します。しかし、そのような行動に頼るのは賢明ではありません。関数の引数の型の
すべての引数を 'int'にキャストしようとはしません。より複雑な引数の昇格スキーム(charとshort-> int、任意の* - > void *、float - > double AFAIR)を使用し、呼び出しを固定arg関数これらの引数で宣言します。そして、それはvararg '(...)'として扱われません。 – Vovanium
キャスト修正に同意します。 varargの説明については、 を参照してください。http://stackoverflow.com/questions/336814/why-include-stdio-h-is-not-required-to-use-printf – imaximchuk
C supprots 3種類:
foo(int x, double y)
:あなたは引数を持つ関数を宣言するとき、これはあります。foo(void)
と混同しない:それは引数なしで最初の形式です)foo()
:あなたは空の括弧でそれを宣言するとき、これは、またはは全くにそれを宣言しません。foo(int x, ...)
で宣言したときです。標準機能が動作していると、機能定義(フォーム1または3)はフォーム2(同じ呼び出し規約を使用)と互換性があります。多くの古い標準。関数の宣言がなく、すべてが形式2になっていた初期のCの形をしているので、ライブラリ関数はそうである(designedされているように)。他の関数は、引数がこのフォームの引数昇格規則で宣言されています。しかし、そうでない人もいます。
しかし、フォーム2では、コンパイラはプロトタイプで引数をチェックすることができず、実際に渡された引数で呼び出し規約を判断する必要があるため、同じ型の引数を渡す必要があります。 (両方の形態1及び2)固定引数関数のMC68000機最初の2つの整数の引数に例えば
、レジスタD0
及びD1
にA0
とA1
の最初の2つのポインタが渡され、他のすべてがスタックに通します。 ptr
count
D1
で、size
D0
で、A0
中とA1
でstream
(と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++
はこれをまったく許可しません。は、既知の引数で関数を宣言する必要があります。
[理由#includeはprintf()を使用する必要はありません?](http://stackoverflow.com/questions/336814/why-include-stdio-h-is-not-required-to- –
vaxquis