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

时间:2015-03-22 04:25:27

标签: c language-lawyer c99 c11

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

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

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

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

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

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

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

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

对所有托管实施都有效且可移植吗?

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

3 个答案:

答案 0 :(得分:23)

没有

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

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

第1段说:

  

程序启动时调用的函数名为 main 。实施宣布否   这个功能的原型。它的定义应为 int 的返回类型,而不是   参数:

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

或有两个参数(此处称为 argc argv ,但任何名称都可能是   使用,因为它们是声明它们的函数的本地函数):

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

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

使用"这个词应该"在约束之外意味着任何 违反它的程序有未定义的行为。所以,例如,如果我写:

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

打印诊断程序并不需要符合标准的编译器,但它是正确的 也不需要编译程序,或者如果它编译 它,让它以任何特定的方式表现。

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

int main(void) { }

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

如果我改为写:

int main() { }

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

作为声明,它没有指定参数的数量或类型 预期的功能。作为定义,它没有定义参数, 但是编译器不需要使用该信息来诊断不正确的呼叫。

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。

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

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

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

main()

int main()

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

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

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

int main() { return 0; }

没有抱怨,行为相当于

int main(void) { return 0; }

但出于各种原因:

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

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

答案 1 :(得分: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;
}
  

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;
}

至于标准的实际规范性文本,我认为过多的东西正在被读入&#34;等同于#34;。应该很清楚

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中被视为等效。严格的英语含义&#34;等同于&#34;不是如何阅读。

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

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

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

答案 2 :(得分:7)

是。

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

相当于

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

N1570 5.1.2.2.1 / 1

  

程序启动时调用的函数名为main。实施宣布否   这个功能的原型。 应使用返回类型int和no来定义   参数

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

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

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

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

6.7.6.3/14

  

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

(强调我的)

正如标准明确指出的那样,定义int main() { /* ... */ } 确实指定功能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参数类型声明符)是一个过时的功能。” (6.11.6),并且因为这种形式的定义不包含函数原型声明符,所以编译器不会检查参数的数量和类型是否正确。

N1570 6.5.2.2/8

  

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

(强调我的)