英語 - 関数宣言と定義C




プロトタイプ宣言 英語 (3)

はい、違います。 2番目のものは正しいです、最初のものは全体として間違っています。 GCC 5.2.1がそれを完全にコンパイルすることを拒んでいるのとても間違っています 。 それがまったくあなたのために働くということは、ほんのflukeです:

/* this coupled with */
int function1();

int main() {
    /* this */
    function1(x, y);
}

/* and this one leads to undefined behaviour */
int function1(int x, float y) {
    /* ... */
}

上記のコードでは、宣言int function1(); 引数の型を指定しない(プロトタイプを持たない)。これは、C11(およびC89、C99では標準)の廃止予定機能と見なされています。 そのような関数が呼び出されると、デフォルトの引数の昇格が引数に対して行われますintはそのまま渡されますが、 floatdouble昇格されます。

実際の関数は(int, double)ではなく(int, double) (int, float)引数を期待しているので、これは未定義の振る舞いを引き起こします。 あなたの関数がa (int, double)期待していたがyが整数だったとしても、あるいはあなたがfunction1(0, 0);それを呼んだと言ったとしてもfunction1(0, 0); function(0, 0.0);代わりにfunction(0, 0.0); あなたのプログラムはまだ未定義の振る舞いをするでしょう。 幸い、GCC 5.2.1はfunction1宣言と定義が矛盾していることに気付きました。

% gcc test.c
test.c:9:5: error: conflicting types for function1
 int function1(int x, float y) {
     ^
test.c:9:1: note: an argument type that has a default promotion cant 
    match an empty parameter name list declaration
 int function1(int x, float y) {
 ^
test.c:1:5: note: previous declaration of function1 was here
 int function1();
     ^
test.c:12:5: error: conflicting types for function2
 int function2(int x, float y) {
     ^
test.c:12:1: note: an argument type that has a default promotion cant 
    match an empty parameter name list declaration
 int function2(int x, float y) {
 ^
test.c:2:5: note: previous declaration of function2 was here
 int function2();
     ^

そして、コンパイラはエラーコードで終了しますが、私のtcc それを楽しくコンパイルします、何の診断も、何もしません。 壊れたコードが生成されるだけです 。 宣言がヘッダーファイルにあり、その宣言が含まれていない別のコンパイル単位に定義されている場合も同様です。

さて、コンパイラがこのケースを検出しなければ、実行時に、 未定義の振る舞いで予想されるように、 何でも起こり得ます

たとえば、引数がスタックに渡された場合を想定します。 32ビットプロセッサでは、 intfloatは4バイトに収まりますが、 doubleは8バイトになります。 関数呼び出しは、たとえfloatであっても、 xintydoubleとしてプッシュします。呼び出し側は合計で12バイトをプッシュし、呼び出し先は8を期待するだけでした。

別の例として、2つの整数を使って関数を呼び出すとします。 呼び出し元のコードはこれらを整数レジスタにロードしますが、呼び出し元は浮動小数点レジスタではdoubleを期待します。 浮動小数点レジスタにトラップ値が含まれていると、アクセス時にプログラムが強制終了されます。

さらに悪いことに、あなたのプログラム 予想通りに振る舞うかもしれません。そのため、コードを新しいバージョンのコンパイラで再コンパイルしたり、他のプラットフォームに移植したりするときに問題を引き起こす可能性のあるheisenbugを含んでいます。

私は私の関数がそのような主な関数の前に宣言されている簡単なコードを持っています:

int function1();
int function2();

int main() {
   /* ... */
   function1(x,y);
   function2(x,y);
   /* .... */
}

int function1(int x, float y) { /* ... */ }
int function2(int x, float y) { /* ... */ }

そして私の主な機能の後に、機能の定義があります。

このようにmainの前に関数を宣言した場合、いくつか違いがありますか?

int function1(int x, float y);
int function2(int x, float y);

int main() {
   /* ... */
   function1(x,y);
   function2(x,y);
   /* .... */
}

int function1(int x, float y) { /* ... */ }
int function2(int x, float y) { /* ... */ }

はい、違います。

最初の例では、関数の名前と戻り値の型についてコンパイラーに指示しているだけで、予期される引数は何もありません。

2番目の例では、関数を呼び出す前に、型と期待される引数の両方を返す関数の完全なシグネチャをコンパイラに伝えています。

2番目の形式は、関数を呼び出すときに間違った型または引数の数があるときにコンパイラが警告するよりよい仕事をするのを助けるので、かなり普遍的に優れています。

また、Cのint function()は、引数を受け入れない関数であり引数を受け入れない関数ではありません 。 そのためには明示的なvoid 、すなわちint function(void)が必要です。 これは主にC++からC++やってくる人たちをつまずかせる。

参照:( 実際の関数定義と比較して)パラメータのない関数はなぜコンパイルされるのですか?

最初の時代遅れの形式が現代のC言語ではなぜ悪いのを示すために、次のプログラムはgcc -Wall -ansi -pedanticまたはgcc -Wall -std=c11を付けて警告なしにコンパイルします。

#include<stdio.h>
int foo();

int main(int argc, char**argv)
{
  printf("%d\n", foo(100));
  printf("%d\n", foo(100,"bar"));
  printf("%d\n", foo(100,'a', NULL));
  return 0;
}

int foo(int x, int y)
{
  return 10;
}

更新: M&Mは、私の例では関数にfloatではなくint使用することにfloatました。 int function1()を宣言することは悪い形式であることに私たち全員が同意することができると思いますが、この宣言が引数を受け入れるという私の主張はまったく正しくありません。 それが事実である理由を説明している関連スペックセクションについてはVladの答えを見てください。


違いは、2番目のコードスニペットのように関数プロトタイプがあることです。次に、コンパイラは引数の数と型がパラメータの数と型に対応していることを確認します。 矛盾が見つかった場合、コンパイラーはコンパイル時にエラーを出すことがあります。

最初のコードスニペットのように関数プロトタイプがない場合、コンパイラは各引数に対してデフォルトの引数の昇格を実行します。これには整数の昇格およびfloat型の式のdouble型への変換が含まれます。 これらの操作の後でプロモートされた引数の数と型がパラメータの数と型に対応していない場合、動作は未定義です。 関数定義は他のコンパイル単位にある可能性があるため、コンパイラーはエラーを出すことができない可能性があります。

これはC標準からの関連する引用です(6.5.2.2関数呼び出し)

2呼び出された関数を表す式がプロトタイプを含む型を持つ場合、引数の数はパラメータの数と一致しなければなりません。 各引数は、その値が、対応するパラメータの型の修飾されていないバージョンを持つオブジェクトに割り当てられるような型を持つものとします。

6呼び出された関数を表す式がプロトタイプを含まない型を持つ場合、整数の昇格は各引数に対して実行され、float型を持つ引数はdoubleに昇格されます。 これらはデフォルトの引数プロモーションと呼ばれます。 引数の数がパラメータの数と等しくない場合、動作は未定義です。 関数がプロトタイプを含む型で定義され、プロトタイプが省略記号(、...)で終了するか、昇格後の引数の型がパラメータの型と互換性がない場合、動作は未定義です。 プロトタイプを含まない型で関数が定義され、昇格後の引数の型が昇格後のパラメータの型と互換性がない場合、次の場合を除いて動作は未定義です。

- 一方のプロモート型は符号付き整数型、もう一方のプロモート型は対応する符号なし整数型で、値は両方の型で表現可能です。

- どちらの型も、文字型またはvoidの修飾バージョンまたは非修飾バージョンへのポインタです。

あなたのコードスニペットに関しては、2番目のパラメータがdouble型であれば、コードは整形式になるでしょう。 ただし、2番目のパラメータはfloat型ですが、対応する引数はdouble型に昇格されるので、最初のコードスニペットの動作は未定義です。





definition