我问,因为我的编译器似乎这么认为,即使我没有。
echo 'int main;' | cc -x c - -Wall
echo 'int main;' | c++ -x c++ - -Wall
Clang没有发出任何警告或错误,gcc只发出温和的警告:'main' is usually a function [-Wmain]
,但仅在编译为C时。指定-std=
似乎并不重要。
否则,它编译并链接正常。但是在执行时,它会立即终止SIGBUS
(对我来说)。
通过What should main() return in C and C++?阅读(优秀)答案并快速浏览语言规范,对我来说肯定似乎主函数是需要。但是来自gcc的-Wmain
('main' 通常 一个函数)的冗语(以及这里的错误缺失)似乎可能暗示其他原因。
但为什么呢?是否有一些奇怪的边缘案例或“历史”用途?有人知道是什么给出的吗?
我想,我的观点是,我认为这应该是托管环境中的错误,是吗?
答案 0 :(得分:97)
由于问题被双重标记为C和C ++,因此C ++和C的推理会有所不同:
xyz
和独立的全局函数xyz(int)
。但是,名称main
永远不会被破坏。这就是这里发生的事情:链接器期望找到符号main
,它确实如此。它"电线"那个符号好像是一个函数,因为它不知道更好。将控制权传递给main
的运行时库部分要求链接器main
,因此链接器为其提供符号main
,让链接阶段完成。当然,这在运行时失败,因为main
不是函数。
以下是同一问题的另一个例子:
file x.c:
#include <stdio.h>
int foo(); // <<== main() expects this
int main(){
printf("%p\n", (void*)&foo);
return 0;
}
文件y.c:
int foo; // <<== external definition supplies a symbol of a wrong kind
编译:
gcc x.c y.c
这会编译,它可能会运行,但它的未定义行为,因为承诺给编译器的符号类型与提供给链接器的实际符号不同。
就警告而言,我认为这是合理的:C允许您构建没有main
函数的库,因此如果您需要,编译器会释放名称main
以用于其他用途由于某种未知原因定义变量main
。
答案 1 :(得分:30)
main
不是保留字它只是预定义标识符(例如cin
,endl
,{{1你可以声明一个名为npos
的变量,初始化它然后打印出它的值。
当然:
main
函数(库)的源文件。修改
一些参考文献:
main()
不是保留字(C ++ 11):
函数
main
不得在程序中使用。联系main
的(3.5)是实现定义的。定义主要的程序 已删除或声明主要为main
,inline
或static
格式不正确。 名称constexpr
不是其他名称 保留。 [示例:成员函数,类和枚举可以是 称为main
,与其他名称空间中的实体一样。 - 结束例子]
C ++ 11 - [basic.start.main] 3.6.1.3
[2.11 / 3] [...]某些标识符保留供C ++实现和标准库(17.6.4.3.2)使用,否则不得使用;无需诊断。
[17.6.4.3.2 / 1] 某些名称和功能签名集始终保留给实现:
- 包含双下划线__或以下划线后跟大写字母(2.12)开头的每个名称都保留给实现以供任何使用。
- 以下划线开头的每个名称都保留给实现,以用作全局命名空间中的名称。
Reserved words in programming languages
程序员可能无法重新定义保留字,但通常可以在某些容量中覆盖预定义字。这是main
的情况:有一些范围,使用该标识符的声明重新定义了它的含义。
答案 2 :(得分:19)
int main;
是一个有效的C / C ++程序吗?
目前还不完全清楚C / C ++程序是什么。
int main;
是否是有效的C程序?
是。允许独立实施接受此类计划。 main
在独立环境中不必具有任何特殊含义。
在托管环境中不有效。
int main;
是一个有效的C ++程序吗?
同上。
为什么会崩溃?
该程序在您的环境中没有意义。在独立环境中,程序启动和终止以及main
的含义是实现定义的。
为什么编译器警告我?
编译器可能会警告您不管它喜欢什么,只要它不拒绝合规程序。另一方面,警告是诊断不合格程序所需的全部内容。由于此转换单元不能是有效托管程序的一部分,因此诊断消息是合理的。
gcc
是一个独立的环境,还是托管环境?
是
gcc
记录了-ffreestanding
编译标志。添加它,警告消失。您可能希望在构建时使用它,例如内核或固件。
g++
未记录此类标记。提供它似乎对此程序没有影响。假设g ++提供的环境是托管的,可能是安全的。在这种情况下缺乏诊断是一个错误。
答案 3 :(得分:17)
这是一个警告,因为它在技术上不被禁止。启动代码将使用“main”的符号位置,并使用三个标准参数(argc,argv和envp)跳转到它。它没有,并且在链接时不能检查它实际上是一个函数,甚至它没有那些参数。这也是int main(int argc,char ** argv)工作的原因 - 编译器不知道envp参数,它恰好不会被使用,而且它是调用者清理。
作为一个笑话,你可以做类似
的事情int main = 0xCBCBCBCB;
在x86机器上,忽略警告和类似的东西,它不仅会编译,而且实际上也可以工作。
有人使用类似于此的技术编写可直接在多个体系结构上运行的可执行文件({3}}。它也被用来赢得IOCCC - http://phrack.org/issues/57/17.html#article。
答案 4 :(得分:9)
这是一个有效的程序吗?
没有的
它不是程序,因为它没有可执行部分。
编译是否有效?
是的
是否可以与有效的程序一起使用?
是的
并非所有已编译的代码都必须可执行才有效。例如静态和动态库。
您已经有效地构建了一个目标文件。它不是有效的可执行文件,但是另一个程序可以通过在运行时加载它来链接到结果文件中的对象main
。
这应该是错误吗?
传统上,C ++允许用户做一些看似没有有效用途但符合语言语法的事情。
我的意思是肯定的,这可能会被重新归类为错误,但为什么呢?警告没有用于什么目的?
只要理论上有可能在实际代码中使用此功能,那么根据语言使用名为main
的非函数对象的可能性很小。
答案 5 :(得分:6)
我想通过引用实际语言标准来添加已经给出的答案。
简短回答(我的观点):只有当您的实施使用&#34;独立执行环境&#34;。
以下来自C11
的引用<强> 5。环境强>
实现转换C源文件并执行 C程序 两个数据处理系统环境,将被称为 翻译环境和执行环境[...]
5.1.2执行环境
定义了两个执行环境:独立和托管。在 两种情况下,程序启动发生在指定的C函数时 由执行环境调用。
5.1.2.1独立环境
在独立环境中(可能需要执行C程序) 没有操作系统任何好处的地方),名称和类型 在程序启动时调用的函数是实现定义的。
5.1.2.2托管环境
无需提供托管环境,但必须符合 以下规格(如果有)。
5.1.2.2.1程序启动
程序启动时调用的函数名为 main 。 [...]它应该 用返回类型int定义,没有参数[...]或 有两个参数[...]或等价物或其他参数 实现定义的方式。
从中可以看出以下情况:
在一个独立的执行环境中,我认为它是一个不允许启动发生的有效程序,因为5.1.2中没有要求的功能。 在托管执行环境中,当您的代码引入一个名为 main 的对象时,它无法提供返回值,所以我认为它在这个意义上不是一个有效的程序,尽管人们也可以争论在此之前,如果程序不是要执行的(例如,可能只想提供数据),那么它就不允许这样做。
简短回答(我的观点):只有当您的实施使用&#34;独立执行环境&#34;。
来自C++14
3.6.1主要功能
程序应包含一个名为main的全局函数,即 指定的程序开始。它是实现定义的 需要在独立环境中定义主程序 功能。 [...]它的返回类型应为int类型,否则为 它的类型是实现定义的。 [...]主要名称不是 另外保留。
这里,与C11标准相反,对独立执行环境的限制较少,因为根本没有提到启动功能,而对于托管执行环境,情况与C11几乎相同。
同样,我认为对于托管案例,您的代码不是有效的C ++ 14程序,但我确信它是针对独立案例的。
由于我的回答仅考虑执行环境,我认为dasblinkenlicht的回答起作用,因为 translation 环境中出现的名称错误事先发生。在这里,我不太确定如此严格地遵守上述引用。
答案 6 :(得分:4)
我想,我的观点是,我认为这应该是托管环境中的错误,是吗?
错误是你的。您没有指定名为main
的函数,该函数返回int
并尝试在托管环境中使用您的程序。
假设您有一个编译单元,它定义了一个名为main
的全局变量。这在独立环境中可能是合法的,因为构成一个程序的是由独立环境中的实现决定的。
假设您有另一个编译单元,它定义了一个名为main
的全局函数,它返回int
并且不带参数。这正是托管环境中的程序所需要的。
如果您只在独立环境中使用第一个编译单元并且仅在托管环境中使用第二个编译单元,那么一切都很好。如果你在一个程序中同时使用两个怎么办在C ++中,您违反了一个定义规则。这是未定义的行为。在C中,您违反了规定所有对单个符号的引用必须一致的规则;如果他们不是未定义的行为。未定义的行为是一个免费的监狱!&#34;卡片给开发者的一个实现。实现为响应未定义的行为而做的任何事情都符合标准。该实现不必警告,更不用说检测未定义的行为。
如果您只使用其中一个编译单元,但使用了错误的编译单元(这就是您所做的),该怎么办?在C中,情况是明确的。未能在托管环境中的两个标准表单之一中定义函数main
是未定义的行为。假设您根本没有定义main
。编译器/链接器并不能说出有关此错误的信息。他们所抱怨的是他们的代表。 C程序编译和链接没有错误是你的错,而不是编译器。
在C ++中不太清楚,因为在托管环境中未能定义函数main
是错误而不是未定义的行为(换句话说,它必须被诊断)。但是,C ++中的一个定义规则意味着链接器可能相当愚蠢。链接器的工作是解析外部引用,并且由于一个定义规则,链接器不必知道这些符号的含义。您提供了一个名为main
的符号,链接器希望看到一个名为main
的符号,因此就链接器而言,所有符号都是好的。
答案 7 :(得分:4)
对于C到目前为止,它是实现定义的行为。
正如ISO / IEC9899所说:
5.1.2.2.1程序启动
1程序启动时调用的函数名为main。实施宣布否 这个功能的原型。它应该使用返回类型int和no来定义 参数:
int main(void) { /* ... */ }
或有两个参数(这里称为argc和argv,但可能有任何名称 使用,因为它们是声明它们的函数的本地函数):
int main(int argc, char *argv[]) { /* ... */ }
或同等的;或者以其他一些实现定义的方式。
答案 8 :(得分:3)
不,这不是一个有效的程序。
对于C ++,最近由defect report 1886: Language linkage for main()明确表示错误,其中说:
对main()提供明确的语言链接似乎没有任何限制,但它可能应该是格式错误或有条件支持。
并且部分决议包括以下更改:
在全局范围内声明变量main或使用C语言链接声明名称main(在任何命名空间中)的程序格式不正确。
我们可以在最新的C++ draft standard N4527中找到这个措辞,这是C ++ 1z草案。
clang和gcc的最新版本现在都会出错( see it live ):
error: main cannot be declared as global variable
int main;
^
在此缺陷报告之前,它是未定义的行为,不需要诊断。另一方面,格式错误的代码需要诊断,编译器可以将此作为警告或错误。