C言語(C11)で`main`の定義方法は何が一番正しいのか?

  • 33
    Like
  • 3
    Comment
More than 1 year has passed since last update.

【注意】下記はC言語での話であって、C++言語の話ではありません。混同しないように!

仕様書を確認する

C11仕様書最終ドラフトn1570には

int main(void) { /* ... */ }
int main(int argc, char *argv[]) { /* ... */ }

の二つだけしか書いていません。また、char *argv[]の部分はchar **argvとしてもよいと備考に書かれています。なお、argcargvは別の名前でもかまいません。ただし、これ以外を禁止しているのでは無く、実装定義の方法でもよいと書いてあります。

では、どうすべきでしょうか?OSやコンパイラによっていちいちmainの書き方を変えるのは、移植性を低くし、不具合の温床とのなります。よって、上の三種類以外は使用すべきではありません。上の三つが正常に動作しないようなコンパイラは消費期限が切れてますので 捨てて下さい

結論

mainを定義するときはint main(void) {}int main(int argc, char *argv[]) {}int main(int argc, char **argv) {}を使いなさい。

ちゃんと確認する

これで終わってしまっては駄目なので、巷の噂にある物を確認してみます。コンパイルはclang -Wall -std=c11 ${name}.c -o ${name}でしてみました。

$ clang --version
Apple LLVM version 6.0 (clang-600.0.56) (based on LLVM 3.5svn)
Target: x86_64-apple-darwin14.1.0
Thread model: posix

検証1 戻り値の型を省略するとintになるので、書かなくてもよい。

省略できるなら、書かなくてもいいよね?

none_main_void.c
main(void)
{
    return 0;
}
$ clang -Wall -std=c11 none_main_void.c -o none_main_void
none_main_void.c:1:1: warning: type specifier missing, defaults to 'int'
      [-Wimplicit-int]
main(void)
^~~~
1 warning generated.

暗黙のintが使われますという警告が表示されますが、一応はコンパイルできました。これはC言語の古い書き方の互換性のために残されている文法です。将来廃止される可能性があり、使うべきではありません。

検証2 戻り値の型はvoidでもよい。

ふるーいC言語入門によくある書き方です。

void_main_void.c
void main(void)
{
    return;
}
$ clang -Wall -std=c11 void_main_void.c -o void_main_void
void_main_void.c:1:1: error: 'main' must return 'int'
void main(void)
^~~~
int
1 error generated.

戻り値の型がintじゃないとエラーになってコンパイルに失敗しました。voidでもよい実装のコンパイラなら通るかもしれませんが、実装依存になってしまいますので、使わないようにしましょう。

検証3 ()とすると(...)と解釈される。

そもそも(...)って何でしょうか?可変長パラメーターの事だとするとva_startの第2パラメーターはどうするでしょうかね?

int_main_empty.c
int main()
{
    return 0;
}
$ clang -Wall -std=c11 int_main_empty.c -o int_main_empty

エラーも何も無しでコンパイルできました。int main(void) {}と逆アセンブリして比較すると全く同じになっています(int main(int argc, char *argv[])は異なります)。あれ、結局、C++と一緒でint main() {}でいいんじゃ無いのか?

いえいえ、そうでありません。C++言語では(void)と同じですが、C言語の()はまた別の意味になります。次のコードを見て下さい。

emp_para.c
int a();

int main(void)
{
    a();
    a(1);
    a(1, 2);
    return 0;
}

int a()
{
    return 0;
}

a()関数にパラメーターを色々渡していますが、エラーになりません。もしint a(void)としていた場合は、a(1)のところでエラーになってしまいます。つまり、()はどんな引数でもかまわないという意味になります。なお、va_startの第2パラメーターに指定する変数が無いので、0個以上の可変長引数としては使えません。そもそも可変長引数は, ...という表記を最後に付けるという定義なので(...)というものはありません。

なお、仕様書には空の()は廃止予定と記載されていますので、使うべきではありません。

検証4 環境変数が入った第3のパラメーターがあるらしい。

第3の男、現る。

int_main_envp.c
int main(int argc, char *argv[], char *envp[])
{
    return 0;
}
$ clang -Wall -std=c11 int_main_envp.c -o int_main_envp

問題なくコンパイルできます。実は、仕様書の付録Jにこのenvpに関する記載があります。envpは環境変数が入っている文字列の配列で、一番最後がNULLになっています。付録に記載されていることもあり、ほとんどの環境で同じように使えるようです。ただ、実装依存であり、全ての環境で使えることが保証されていないため、使用しない方がいいでしょう。環境変数はgetenv()等で操作をして下さい。

結論

main() {}でも動くけど、ある日突然エラーになっても知らないからね。