このコードがコンパイルされる理由がわかりません。
#include <stdio.h>
void foo() {
printf("Hello\n");
}
int main() {
const char *str = "bar";
foo(str);
return 0;
}
gcc は、foo() にあまりにも多くの引数を渡しているという警告さえスローしません。これは予想される動作ですか?
このコードがコンパイルされる理由がわかりません。
#include <stdio.h>
void foo() {
printf("Hello\n");
}
int main() {
const char *str = "bar";
foo(str);
return 0;
}
gcc は、foo() にあまりにも多くの引数を渡しているという警告さえスローしません。これは予想される動作ですか?
C では、空のパラメーター リストで宣言された関数は、呼び出されるときに任意の数の引数を受け入れます。これらの引数は、通常の算術昇格の対象となります。提供された引数が関数の定義に適していることを確認するのは、呼び出し元の責任です。
引数を取らない関数を宣言するには、 と書く必要がありますvoid foo(void);
。
これは歴史的な理由によるものです。元々、Cは型のない言語であるBから進化したため、C 関数にはプロトタイプがありませんでした。プロトタイプが追加されたとき、下位互換性のために元の型のない宣言が言語に残されました。
空のパラメーター リストについて gcc に警告させるには、次を使用します-Wstrict-prototypes
。
引数の型を指定せずに関数が宣言または定義されている場合に警告します。(古いスタイルの関数定義は、引数の型を指定する宣言が前にある場合、警告なしで許可されます。)
従来の理由から、()
for パラメーター リストを使用して関数を宣言することは、本質的に「関数が呼び出されたときにパラメーターを把握する」ことを意味します。関数にパラメーターがないことを指定するには、 を使用します(void)
。
編集:私はこの問題で、年をとっているという評判を上げているように感じます. プログラミングがどのようなものだったかを子供たちに知ってもらうために、これが私の最初のプログラムです。(C ではありません。その前に作業しなければならなかったものを示しています。)
void foo() {
printf("Hello\n");
}
foo(str);
Cでは、このコードは制約に違反していません(プロトタイプ形式でで定義されている場合void foo(void) {/*...*/}
)。制約違反がないため、コンパイラーは診断を発行する必要がありません。
ただし、このプログラムは、次のCルールに従って未定義の動作をします。
から:
(C99、6.9.1p7)「宣言子にパラメーター型リストが含まれている場合、リストにはすべてのパラメーターの型も指定されます。このような宣言子は、同じ変換単位内の同じ関数を後で呼び出すための関数プロトタイプとしても機能します。宣言者に識別子リストが含まれている場合142)、パラメータのタイプは次の宣言リストで宣言されるものとします。」
関数はプロトタイプを提供しfoo
ません。
から:
(C99、6.5.2.2p6)「呼び出された関数を表す式の型がプロトタイプを含まない場合[...]引数の数がパラメーターの数と等しくない場合、動作は未定義です。」
foo(str)
関数呼び出しは未定義の動作です。
Cは、未定義動作を呼び出すプログラムの診断を発行するように実装を義務付けていませんが、プログラムは依然として誤ったプログラムです。
C99 標準 (6.7.5.3) と C11 標準 (6.7.6.3) の両方の状態:
識別子リストは、関数のパラメーターの識別子のみを宣言します。その関数の定義の一部である関数宣言子の空のリストは、関数にパラメーターがないことを指定します。その関数の定義の一部ではない関数宣言子の空のリストは、パラメーターの数または型に関する情報が提供されないことを指定します。
foo の宣言は定義の一部であるため、宣言は foo が 0 引数を取ることを指定しているため、呼び出し foo(str) は少なくとも道徳的に間違っています。ただし、以下で説明するように、C にはさまざまな程度の「誤り」があり、コンパイラは、特定の種類の「誤り」を処理する方法が異なる場合があります。
もう少し単純な例として、次のプログラムを考えてみましょう。
int f() { return 9; }
int main() {
return f(1);
}
Clang を使用して上記をコンパイルすると、次のようになります。
tmp$ cc tmp3.c
tmp3.c:4:13: warning: too many arguments in call to 'f'
return f(1);
~ ^
1 warning generated.
gcc 4.8 でコンパイルすると、-Wall を使用しても、エラーや警告は表示されません。f の定義がプロトタイプ形式ではないことを正しく報告する -Wstrict-prototypes の使用が以前の回答で提案されましたが、これは実際には重要ではありません。C 標準では、上記のような非プロトタイプ形式の関数定義を許可しており、標準では、この定義で関数が引数を 0 とることを指定していると明確に述べています。
現在、制約があります(C11 Sec. 6.5.2.2):
呼び出される関数を表す式がプロトタイプを含む型を持つ場合、引数の数はパラメーターの数と一致する必要があります。
ただし、この場合、関数の型にはプロトタイプが含まれないため、この制約は適用されません。ただし、セマンティクス セクションの後続のステートメント (「制約」ではありません) は次のとおりです。
呼び出される関数を表す式がプロトタイプを含まない型の場合 ... 引数の数がパラメーターの数と等しくない場合、動作は未定義です。
したがって、関数呼び出しは未定義の動作を引き起こします (つまり、プログラムは「厳密に準拠」していません)。ただし、標準では、実装が実際の制約に違反した場合にのみ診断メッセージを報告する必要があり、この場合、制約違反はありません。したがって、gcc は「適合する実装」であるためにエラーや警告を報告する必要はありません。
なぜ gcc がそれを許可するのかという質問への答えは、これは制約違反ではないため、gcc は何も報告する必要がないということだと思います。さらに、gcc は、-Wall や -Wpedantic を使用しても、あらゆる種類の未定義の動作を報告するとは主張していません。これは未定義の動作です。これは、実装がそれを処理する方法を選択できることを意味し、gcc は警告なしでコンパイルすることを選択しました (そして明らかに引数を無視するだけです)。