int main(){}(没有“void”)在ISO C中是否有效且可移植?

C标准为托管实现指定了两种main的定义forms:

 int main(void) { /* ... */ } 

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

它可以以与上述“等效”的方式定义(例如,您可以更改参数名称,将int替换为定义为int的typedef名称,或将char *argv[]替换为char **argv )。

它也可以“以某种其他实现定义的方式”定义 – 这意味着如果实现记录它们int main(int argc, char *argv[], char *envp)是有效的。

“在其他一些实施定义的方式”条款不在1989/1990标准中; 它是由1999标准添加的(但早期标准允许扩展,因此实现仍然允许其他forms)。

我的问题是:鉴于当前(2011)ISO C标准,是表格的定义

 int main() { /* ... */ } 

对所有托管实现有效且可移植?

(请注意,我没有解决void main或在C ++中没有使用括号的int main() 。这只是ISO C中的int main(void)int main()之间的区别。)

没有。

根据标准的规范性措辞,使用没有void关键字的空括号的定义不是必须接受的forms之一,严格来说,这种程序的行为是不确定的。

参考: N1570第5.1.2.2.1节。 (公布的2011 ISO C标准,不是免费提供的,与N1570草案具有相同的措辞。)

第1段说:

程序启动时调用的函数名为main 。 该实现声明此函数没有原型。 它应该使用返回类型int并且没有参数来定义:

 int main(void) { /* ... */ } 

或者使用两个参数(这里称为argcargv ,尽管可以使用任何名称,因为它们是声明它们的函数的本地名称):

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

或同等学历; 或者以其他一些实现定义的方式。

在约束之外使用“shall”一词意味着任何违反它的程序都有未定义的行为。 所以,例如,如果我写:

 double main(unsigned long ocelots) { return ocelots / 3.14159; } 

一个符合标准的编译器不需要打印诊断程序,但它也不需要编译程序,或者如果编译它,也不需要让它以任何特定的方式运行。

如果int main() 等效int main(void) ,那么它对任何符合标准的托管实现都是有效且可移植的。 但它并不等同。

 int main(void) { } 

提供声明 (在本例中为原型)和定义 。 声明通过使用void关键字指定该函数没有参数。 该定义指定了相同的内容。

如果我反而写:

 int main() { } 

然后我使用旧式声明和定义。 (这样的声明和定义已经过时 ,但它们仍然是语言定义的一部分,所有符合要求的编译器仍然必须支持它们。)

作为声明,它不指定函数所期望的参数的数量或类型。 作为定义,它不定义任何参数,但编译器不需要使用该信息来诊断不正确的调用。

DR#317包括C标准委员会2006年的裁决,其中()的定义不提供与(void)相同的原型(感谢hvd用于查找该引用)。

C允许以递归方式调用main。 假设我写道:

 int main(void) { if (0) { main(42); } } 

可见原型int main(void)指定main不带参数。 尝试传递一个或多个参数的调用违反约束,需要编译时诊断。

或者假设我写道:

 int main() { if (0) { main(42); } } 

如果执行了调用main(42) ,它将具有未定义的行为 – 但它不违反约束,并且不需要诊断。 由于它受if (0)保护,因此调用永远不会发生,并且未定义的行为永远不会发生。 如果我们假设int main()是有效的,那么任何符合标准的编译器都必须接受该程序。 但正因为如此,它表明int main() 等于int main(void) ,因此不在5.1.2.2.1中。

结论:遵循标准的措辞,允许实现文档允许int main() { } 。 如果它没有记录,它仍然允许在没有投诉的情况下接受它。 但是,符合标准的编译器也可以拒绝 int main() { } ,因为它不是标准允许的forms之一,因此其行为是未定义的。

但仍有一个悬而未决的问题:这是标准作者的意图吗?

在1989 ANSI C标准发布之前, void关键字不存在。 预ANSI(K&R)C程序将main定义为

 main() 

或者作为

 int main() 

ANSI标准的主要目标是在破坏现有ANSI前代码的情况下添加新function(包括原型)。 声明int main()不再有效会违反该目标。

我怀疑C标准的作者并不打算使int main()无效。 但是所写的标准并没有反映出这种意图; 它至少允许符合要求的C编译器拒绝int main()

实际上 ,你几乎肯定可以逃脱它。 我曾尝试过的每个C编译器都会接受

 int main() { return 0; } 

没有抱怨,行为相当于

 int main(void) { return 0; } 

但由于各种原因:

  • 遵循标准的文字和意图;
  • 避免使用过时function(未来的标准可能会删除旧式function定义);
  • 保持良好的编码习惯( ()(void)之间的差异对于main以外的函数很重要,这些函数实际上被其他函数调用

我建议总是写int main(void)而不是int main() 。 它更明确地表明了意图,并且您可以100%确定您的编译器将接受它,而不是99.9%。

强调指示int main()是有效的,无论标准是否准确地赋予措辞使其有效,事实是int main()偶尔在标准中使用而没有任何人提出任何异议。 虽然示例不是规范性的,但它们确实表明了意图。

6.5.3.4 sizeof和_Alignof运算符

8示例3在此示例中,计算可变长度数组的大小并从函数返回:

 #include  size_t fsize3(int n) { char b[n+3]; // variable length array return sizeof b; // execution time sizeof } int main() { size_t size; size = fsize3(10); // fsize3 returns 13 return 0; } 

6.7.6.3函数声明符(包括原型)

20示例4以下原型具有可变修改的参数。

 void addscalar(int n, int m, double a[n][n*m+300], double x); int main() { double b[4][308]; addscalar(4, 2, b, 2.17); return 0; } void addscalar(int n, int m, double a[n][n*m+300], double x) { for (int i = 0; i < n; i++) for (int j = 0, k = n*m+300; j < k; j++) // a is a pointer to a VLA with n*m+300 elements a[i][j] += x; } 

至于标准的实际规范性文本,我认为太多被读入“等价”。 应该很清楚

 int main (int argc, char *argv[]) { (void) argc; (void) argv; return 0; } 

是有效的,那

 int main (int x, char *y[]) { (void) argc; (void) argv; return 0; } 

是无效的。 尽管如此,标准在规范性文本中明确指出可以使用任何名称,这意味着int main (int argc, char *argv[])int main (int x, char *y[])计为等效用途5.1.2.2.1。 “等同”一词的严格英文含义并不是它的意思。

Keith Thompson在他的回答中提出了一个稍微宽松的解释。

对该单词的同样有效甚至更宽松的解释允许int main()int main(void)int main()main定义为返回int且不带参数的函数。

标准或任何官方DR目前都没有回答哪个解释的意图,所以问题是无法回答的,但这些例子强烈建议最后的解释。

是。

 int main() { /* ... */ } 

相当于

 int main(void) { /* ... */ } 

N1570 5.1.2.2.1 / 1

程序启动时调用的函数名为main。 该实现声明此函数没有原型。 它应该使用返回类型int并且没有参数来定义

 int main(void) { /* ... */ } 

或者使用两个参数(这里称为argc和argv,尽管可以使用任何名称,因为它们是声明它们的函数的本地名称):

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

或同等学历; 或者以其他一些实现定义的方式。

6.7.6.3/14

标识符列表仅声明函数参数的标识符。 函数声明符中的空列表是该函数定义的一部分,指定该函数没有参数。 函数声明符中的空列表不是该函数定义的一部分,它指定不提供有关参数数量或类型的信息。

(强调我的)

正如标准明确指出的那样,定义int main() { /* ... */ } 确实指定了funtion main没有参数。 我们所有人都清楚,这个函数定义确实指定函数main的返回类型是int 。 并且,由于5.1.2.2.1不要求main的声明有原型,我们可以安全地确认定义int main() { /* ... */ }满足标准规定的所有要求( It [the main funtion] shall be defined with a return type of int and with no parameters, or [some other forms] . )。

尽管如此,你不应该在你的代码中使用int main() {} ,因为“使用带有空括号的函数声明符(而不是prototype-format参数类型声明符)是一个过时的function。” (6.11.6),并且因为这种forms的定义不包含函数原型声明符,所以编译器不会检查参数的数量和类型是否正确。

N1570 6.5.2.2/8

没有其他转换是隐式执行的; 特别是, 参数的数量和类型与不包含函数原型声明符的函数定义中的参数的数量和类型不进行比较

(强调我的)