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且没有参数:Run Code Online (Sandbox Code Playgroud)int main(void) { /* ... */ }或者有两个参数(这里称为
argc和argv,虽然可以使用任何名称,因为它们是声明它们的函数的本地名称):Run Code Online (Sandbox Code Playgroud)int main(int argc, char *argv[]) { /* ... */ }或同等学历; 或者以某种其他实现定义的方式.
在约束之外使用"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%.
小智 13
int main()无论标准是否准确地使措辞有效,强有力的指示都是有效的,这int main()是标准中偶尔使用的事实,没有任何人提出任何异议.虽然示例不是规范性的,但它们确实表明了意图.
6.5.3.4 sizeof和_Alignof运算符
8示例3在此示例中,计算可变长度数组的大小并从函数返回:
Run Code Online (Sandbox Code Playgroud)#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以下原型具有可变修改的参数.
Run Code Online (Sandbox Code Playgroud)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;
}
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目前都没有回答哪个解释的意图,所以问题是无法回答的,但这些例子强烈建议最后的解释.
是.
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的返回类型定义,并且没有参数:
Run Code Online (Sandbox Code Playgroud)int main(void) { /* ... */ }或者有两个参数(这里称为argc和argv,虽然可以使用任何名称,因为它们是声明它们的函数的本地名称):
Run Code Online (Sandbox Code Playgroud)int main(int argc, char *argv[]) { /* ... */ }或同等学历; 或者以某种其他实现定义的方式.
6.7.6.3/14
标识符列表仅声明函数参数的标识符.函数声明符中的空列表是该函数定义的一部分,指定该函数没有参数.函数声明符中的空列表不是该函数定义的一部分,它指定不提供有关参数数量或类型的信息.
(强调我的)
正如标准中明确指出的那样,该定义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
没有其他转换是隐式执行的; 特别是,参数的数量和类型不会与函数定义中不包含函数原型声明符的参数的数量和类型进行比较.
(强调我的)