より良いもの:void foo()
またはvoid foo(void)
? voidを使用すると、見苦しく、一貫性がなくなりますが、良いと言われています。これは本当ですか?
編集:いくつかの古いコンパイラは奇妙なことをすることを知っていますが、GCCだけを使用している場合、void foo()
は大丈夫ですか? foo(bar);
は受け入れられますか?
void foo(void);
これは、Cで「パラメータなし」と言う正しい方法であり、C++でも機能します。
しかし:
void foo();
CとC++では異なることを意味します! Cでは「不明な型のパラメーターをいくつでも取ることができます」という意味で、C++ではfoo(void)
と同じ意味です。
可変引数リスト関数は本質的にタイプセーフではないため、可能な場合は使用しないでください。
Cでパラメーターを指定するには、2つの方法があります。1つは識別子リストを使用する方法で、もう1つはパラメータータイプリストを使用する方法です。識別子リストは省略できますが、タイプリストは省略できません。したがって、1つの関数が関数定義で引数を取らないと言うには、(省略された)識別子リストを使用してこれを行います
void f() {
/* do something ... */
}
そして、これはパラメータタイプリストで:
void f(void) {
/* do something ... */
}
パラメータタイプリストで唯一のパラメータタイプがvoidである場合(名前がない場合)、関数は引数を取らないことを意味します。ただし、関数を定義するこれらの2つの方法には、宣言する内容に違いがあります。
1つ目は、関数が特定の数の引数を取ることを定義しますが、識別子リストを使用するすべての関数宣言と同様に、カウントも必要なもののタイプも伝達されません。そのため、呼び出し元は事前に型とカウントを正確に知る必要があります。したがって、呼び出し元が引数を指定して関数を呼び出す場合、動作は未定義です。たとえば、呼び出された関数が制御を取得するときに異なるレイアウトを予期するため、スタックが破損する可能性があります。
関数パラメーターで識別子リストを使用することは非推奨です。昔は使用されていましたが、まだ多くの製品コードに存在しています。これらは、これらの引数の昇格(昇格した引数の型が関数定義のパラメーターの型と一致しない場合、動作も未定義です)のために深刻な危険を引き起こす可能性があり、もちろん安全性ははるかに劣ります。したがって、パラメータのない関数には、関数の宣言と定義の両方で、常にvoid
thingyを使用してください。
2番目の関数は、関数がゼロの引数を取り、それを通信することを定義します-関数がprototype
と呼ばれるパラメーター型リストを使用して宣言されるすべての場合と同様に。呼び出し元が関数を呼び出して引数を与えると、それはエラーになり、コンパイラーは適切なエラーを出力します。
関数を宣言する2番目の方法には、多くの利点があります。もちろん、パラメータの量と種類がチェックされます。もう1つの違いは、コンパイラはパラメーターの型を知っているため、引数の暗黙的な変換をパラメーターの型に適用できることです。パラメータタイプリストが存在しない場合は実行できず、引数は昇格されたタイプに変換されます(デフォルトの引数プロモーションと呼ばれます)。たとえば、char
はint
になり、float
はdouble
になります。
ところで、ファイルに省略された識別子リストとパラメータータイプリストの両方が含まれている場合、パラメータータイプリストが「勝ちます」。最後の関数の型にはプロトタイプが含まれています。
void f();
void f(int a) {
printf("%d", a);
}
// f has now a prototype.
それは、両方の宣言が矛盾することを何も言っていないからです。しかし、2番目にはさらに何か言いたいことがありました。これは、1つの引数が受け入れられるということです。同じことが逆に行うことができます
void f(a)
int a;
{
printf("%d", a);
}
void f(int);
1つ目は識別子リストを使用して関数を定義し、2つ目はパラメータタイプリストを含む宣言を使用して関数のプロトタイプを提供します。
void foo(void)
は、パラメーターが許可されていないことを明示的に示しているため、より優れています。
void foo()
は、少なくとも一部のコンパイラでは、少なくとも定義ではなく関数の宣言である場合、パラメータを送信できることを意味します。
C99引用符
この回答は、 C99 N1256標準ドラフト の関連部分を引用して説明することを目的としています。
宣言子の定義
declaratorという用語がたくさん登場するので、理解しましょう。
言語の文法から、次のアンダーライン文字が宣言子であることがわかります。
int f(int x, int y);
^^^^^^^^^^^^^^^
int f(int x, int y) { return x + y; }
^^^^^^^^^^^^^^^
int f();
^^^
int f(x, y) int x; int y; { return x + y; }
^^^^^^^
宣言子は、関数の宣言と定義の両方の一部です。
宣言子には2つのタイプがあります。
パラメータタイプリスト
宣言は次のようになります。
int f(int x, int y);
定義は次のようになります。
int f(int x, int y) { return x + y; }
各パラメーターのタイプを指定する必要があるため、パラメータータイプリストと呼ばれます。
識別子リスト
定義は次のようになります。
int f(x, y)
int x;
int y;
{ return x + y; }
宣言は次のようになります。
int g();
空でない識別子リストを持つ関数を宣言することはできません:
int g(x, y);
6.7.5.3 "関数宣言子(プロトタイプを含む)"の理由:
3その関数の定義の一部ではない関数宣言子の識別子リストは空でなければなりません。
f(x, y)
の識別子x
およびy
のみを指定するため、識別子リストと呼ばれます。タイプは後に続きます。
これは古い方法であり、使用しないでください。 6.11.6関数宣言子言う:
1空の括弧を持つ関数宣言子(プロトタイプ形式のパラメーター型宣言子ではない)の使用は、廃止された機能です。
また、Introductionは、廃止予定機能とは何かを説明しています:
特定の機能は廃止されているため、この国際規格の将来の改訂版で廃止される可能性があります。それらは広く使用されているため保持されていますが、新しい実装(実装機能用)または新しいプログラム(言語[6.11]またはライブラリ機能[7.26])での使用は推奨されていません
f()vs宣言のf(void)(
ただ書くとき:
void f();
6.7.5「宣言子」は文法を次のように定義しているため、識別子リスト宣言である必要があります。
direct-declarator:
[...]
direct-declarator ( parameter-type-list )
direct-declarator ( identifier-list_opt )
そのため、識別子リストバージョンのみがオプション(_opt
)であるため、空にできます。
direct-declarator
は、宣言子の(...)
部分を定義する唯一の文法ノードです。
それでは、パラメータなしでより良いパラメータタイプリストをどのように明確にし、使用するのでしょうか? 6.7.5.3関数宣言子(プロトタイプを含む)言います:
10リスト内の唯一の項目としてのvoid型の名前のないパラメーターの特殊なケースは、関数にパラメーターがないことを指定します。
そう:
void f(void);
方法です。
これは、void
型の引数を他の方法で使用できないため、明示的に許可されている魔法の構文です。
void f(void v);
void f(int i, void);
void f(void, int);
f()宣言を使用するとどうなりますか?
たぶんコードはうまくコンパイルされるでしょう:6.7.5.3関数宣言子(プロトタイプを含む):
14その関数の定義の一部ではない関数宣言子の空のリストは、パラメーターの数またはタイプに関する情報が提供されないことを指定します。
だからあなたはで逃げることができます:
void f();
void f(int x) {}
また、UBが忍び寄ることもあります(運がよければ、コンパイラが教えてくれます)。その理由を理解するのは難しいでしょう:
void f();
void f(float x) {}
参照: 空の宣言がint引数を持つが、float引数ではないのはなぜですか?
f()およびf(void) for definition
f() {}
対
f(void) {}
似ていますが、同一ではありません。
6.7.5.3関数宣言子(プロトタイプを含む)言います:
14関数の定義の一部である関数宣言子の空のリストは、その関数にパラメーターがないことを指定しています。
f(void)
の説明に似ています。
しかし、still ...
int f() { return 0; }
int main(void) { f(1); }
未定義の動作に準拠していますが、
int f(void) { return 0; }
int main(void) { f(1); }
なぜgccは引数なしで定義された関数に引数を渡すことができるのですか?
TODOはその理由を正確に理解しています。プロトタイプであるかどうかに関係しています。プロトタイプを定義します。
構文上の違いに加えて、多くの人々は実際的な理由でvoid function(void)
を使用することも好みます:
検索関数を使用していて、関数の実装を見つけたい場合は、function(void)
を検索すると、プロトタイプと実装が返されます。
2番目のvoid
を省略した場合、function()
を検索する必要があるため、すべての関数呼び出しも検出されるため、実際の実装を見つけるのが難しくなります。
C++では、main()
とmain(void)
にnoの違いがあります。
しかし Cでは、main()
はany個のパラメーターで呼び出されます。
例:
main ( ){
main(10,"abc",12.28);
//Works fine !
//It won't give the error. The code will compile successfully.
//(May cause Segmentation fault when run)
}
main(void)
は、パラメータなしで呼び出されます。渡そうとすると、コンパイラエラーが発生します。
例:
main (void) {
main(10,"abc",12.13);
//This throws "error: too many arguments to function ‘main’ "
}