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

Is int main() { } (without "void") valid and portable in ISO C?

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

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

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

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

也可以定义"in some other implementation-defined manner" -- 这意味着像 int main(int argc, char *argv[], char *envp) 这样的东西是有效的 如果 实现记录了它们。

"in some other implementation-defined manner" 子句不在 1989/1990标准;它是由 1999 标准添加的(但 早期的标准允许扩展,因此实现可以 仍然允许其他形式)。

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

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

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

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

没有

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

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

第 1 段说:

The function called at program startup is named main. The implementation declares no prototype for this function. It shall be defined with a return type of int and with no parameters:

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

or with two parameters (referred to here as argc and argv, though any names may be used, as they are local to the function in which they are declared):

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

or equivalent; or in some other implementation-defined manner.

在约束之外使用 "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() { },因为它不是允许的形式之一 标准,因此它的行为是未定义的。

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

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

main()

int main()

ANSI 标准的一个主要目标是添加新功能(包括 prototypes) without breaking existing pre-ANSI code.说明 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%。

是的。

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

相当于

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

N1570 5.1.2.2.1/1

The function called at program startup is named main. The implementation declares no prototype for this function. It shall be defined with a return type of int and with no parameters:

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

or with two parameters (referred to here as argc and argv, though any names may be used, as they are local to the function in which they are declared):

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

or equivalent; or in some other implementation-defined manner.

6.7.6.3/14

An identifier list declares only the identifiers of the parameters of the function. An empty list in a function declarator that is part of a definition of that function specifies that the function has no parameters. The empty list in a function declarator that is not part of a definition of that function specifies that no information about the number or types of the parameters is supplied.

(强调我的)

正如标准明确指出的那样,定义 int main() { /* ... */ } 确实 指定函数 main 没有参数。而且我们都很清楚,这个函数定义确实指定函数main的return类型是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() {},因为 "The use of function declarators with empty parentheses (not prototype-format parameter type declarators) is an obsolescent feature." (6.11.6),并且由于这种形式的定义不包含函数原型声明符,因此编译器不会检查参数的数量和类型是否正确

N1570 6.5.2.2/8

No other conversions are performed implicitly; in particular, the number and types of arguments are not compared with those of the parameters in a function definition that does not include a function prototype declarator.

(强调我的)

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

6.5.3.4 The sizeof and _Alignof operators

8 EXAMPLE 3 In this example, the size of a variable length array is computed and returned from a function:

#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 Function declarators (including prototypes)

20 EXAMPLE 4 The following prototype has a variably modified parameter.

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 目前都没有回答预期使用哪种解释的问题,因此该问题无法回答,但示例强烈表明最后的解释。