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

Kei*_*son 22 c c99 language-lawyer c11

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

int main(void) { /* ... */ }
Run Code Online (Sandbox Code Playgroud)

int main(int argc, char *argv[]) { /* ... */ }
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

int main() { /* ... */ }
Run Code Online (Sandbox Code Playgroud)

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

(请注意,我没有解决C++中的任何一个void main或没有使用 int main()括号的问题.这只是ISO int main(void)int main()ISO 之间的区别.)

Kei*_*son 23

没有.

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

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

第1段说:

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

int main(void) { /* ... */ }
Run Code Online (Sandbox Code Playgroud)

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

int main(int argc, char *argv[]) { /* ... */ }
Run Code Online (Sandbox Code Playgroud)

或同等学历; 或者以某种其他实现定义的方式.

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

double main(unsigned long ocelots) { return ocelots / 3.14159; }
Run Code Online (Sandbox Code Playgroud)

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

如果int main()等同int main(void),那么这将是有效的,并移植到任何符合托管的实现.但它并不等同.

int main(void) { }
Run Code Online (Sandbox Code Playgroud)

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

如果我改为写:

int main() { }
Run Code Online (Sandbox Code Playgroud)

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

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

DR#317包括C标准委员会2006年的裁决,该定义与()未提供相当于一个的原型(void)(感谢hvd用于查找该参考).

C允许main递归调用.假设我写道:

int main(void) {
    if (0) {
        main(42);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

或者假设我写道:

int main() {
    if (0) {
        main(42);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

main()
Run Code Online (Sandbox Code Playgroud)

或者作为

int main()
Run Code Online (Sandbox Code Playgroud)

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

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

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

int main() { return 0; }
Run Code Online (Sandbox Code Playgroud)

没有抱怨,行为相当于

int main(void) { return 0; }
Run Code Online (Sandbox Code Playgroud)

但出于各种原因:

  • 遵循标准的文字和意图;
  • 避免使用过时的功能(未来的标准可以删除旧式的功能定义);
  • 保持良好的编码习惯(除此之外的功能之间的区别()(void)重要性main 实际上是由其他功能调用的

我建议总是写作int main(void)而不是int main().它更清楚地表明了意图,你可以100%确定你的编译器会接受它,而不是99.9%.

  • 重新说明我之前的评论:`int main()`不等同于`int main(void)`作为声明(如你的答案所示),但仍然等同于它作为定义的论点,实际上并非如此你的回答证明或反驳了.是的,`int main(){if(0){main(42); `不需要诊断,但这是因为声明与`int main(void)`不同,并且仍然存在定义相等的潜在合法参数. (7认同)
  • 此外,我非常*怀疑你的"避免使用过时的功能(未来的标准可能会删除旧式的功能定义);" 论点.即使未来的标准删除旧式函数定义,未来的标准肯定会使`()`等同于`(void)`就像C++一样.认为未来的标准可能不这样做就像争辩说未来的标准可能会使所有整数文字成为基础11.当然,技术上我们不能排除它,但是来吧,我们都知道这不会发生. (5认同)
  • 我会讨厌允许`int main()`的解释,标准是有缺陷的 (4认同)
  • 我想添加一些非常糟糕的东西; 在为他们编写的不太合规的编译器和教程的日子里长大的; 但是,你真的完全覆盖了这个地方:) (3认同)
  • [C99理由](http://www.open-std.org/jtc1/sc22/wg14/www/C99RationaleV5.10.pdf)可能会略微澄清意图.程序执行说:"`main`是唯一可以用零或两个参数声明的函数.(其他函数参数的数量必须在调用和定义之间完全匹配.)这种特殊情况简单地认识到了当程序不访问程序参数字符串时,将参数留给main.[续]" (2认同)
  • 文本“It should be Defined with a return type of int and with noparameters: `int main(void) { /* ... */ }`”显然有缺陷。目前尚不清楚代码示例是否是唯一允许的形式,或者它是否只是允许形式的示例;在这种情况下, `int main() {}` 也满足文本中的条件。 (2认同)

小智 13

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

6.5.3.4 sizeof和_Alignof运算符

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

#include <stddef.h>

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;
}
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

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

int main (int argc, char *argv[]) {
    (void) argc; (void) argv;
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

是有效的,那

int main (int x, char *y[]) {
    (void) argc; (void) argv;
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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


cpp*_*ner 7

是.

int main() { /* ... */ }
Run Code Online (Sandbox Code Playgroud)

相当于

int main(void) { /* ... */ }
Run Code Online (Sandbox Code Playgroud)

N1570 5.1.2.2.1/1

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

int main(void) { /* ... */ }
Run Code Online (Sandbox Code Playgroud)

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

int main(int argc, char *argv[]) { /* ... */ }
Run Code Online (Sandbox Code Playgroud)

或同等学历; 或者以某种其他实现定义的方式.

6.7.6.3/14

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

(强调我的)

正如标准中明确指出的那样,该定义int main() { /* ... */ } 确实指定了功能main没有参数.很显然我们所有的人,这个函数的定义指定该函数的返回类型mainint.并且,由于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参数类型声明符)是一个过时的功能." (6.11.6),并且因为这种形式的定义不包含函数原型声明符,所以编译器不会检查参数的数量和类型是否正确.

N1570 6.5.2.2/8

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

(强调我的)

  • @PascalCuoq Keith Thompson是对的,虽然我同意标准可以更清楚.有关官方解释,请参阅[DR#317](http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_317.htm). (3认同)
  • @hvd实际上,我认为这个答案强调了句子"如果我们假设int main()是有效的,那么这个程序必须被任何符合标准的编译器接受."正如Keith Thompson所说的那样,答案是错误的.因为"函数声明符中的空列表是该函数定义的一部分,指定该函数没有参数". (2认同)