是'int main' 一个有效的C / C ++程序?
我问,因为我的编译器似乎这样认为,即使我不这样认为。
echo 'int main;' | cc -xc - -Wall
echo 'int main;' | c++ -x c++ - -Wall
Clang不会发出任何警告或错误,而gcc只会发出温和的警告: 'main' is usually a function [-Wmain]
,但只有编译为C.指定-std=
似乎并不重要。
否则,它编译和链接罚款。 但是在执行时,它会立即用SIGBUS
(对我来说)终止。
什么应该main()返回在C和C + + (优秀)答案阅读? 并通过语言规范快速grep,它肯定会在我看来,主要function是必需的。 但gcc的-Wmain
( 通常是一个函数)的错误(这里错误的-Wmain
)似乎可能是-Wmain
。
但为什么? 是否有一些奇怪的边缘或“历史”使用这个? 任何人都知道什么给?
我想我的观点是,我真的认为这应该是一个托pipe环境中的错误 ,呃?
由于这个问题被双重标记为C和C ++,所以C ++和C的推理是不同的:
- C ++使用名称修改来帮助链接器区分不同types的文本相同的符号,例如全局variables
xyz
和独立的全局函数xyz(int)
。 但是,名字main
不会被破坏。 - C不使用补码,所以程序可以通过提供一种代替不同符号的符号来混淆链接器,并使程序成功链接。
这就是发生了什么事情:链接器期望find符号main
,并且它确实。 它把这个符号“连线”,就好像它是一个函数,因为它不知道更好。 将控制传递给main
的运行时库部分要求链接器为main
,所以链接器给它的符号main
,让链接阶段完成。 当然这在运行时会失败,因为main
不是函数。
这是同一个问题的另一个例子:
文件xc:
#include <stdio.h> int foo(); // <<== main() expects this int main(){ printf("%p\n", (void*)&foo); return 0; }
文件yc:
int foo; // <<== external definition supplies a symbol of a wrong kind
编译:
gcc xc yc
这个编译,它可能会运行,但它是未定义的行为,因为承诺给编译器的符号的types不同于提供给链接器的实际符号。
至于警告,我认为这是合理的:C允许你build立没有main
函数的库,所以如果你需要定义一个variablesmain
出于某些未知的原因,编译器将释放name作为其他用途。
main
不是保留字,它只是一个预定义的标识符 (如cin
, endl
, npos
…),所以你可以声明一个名为main
的variables,初始化它,然后打印出它的值。
当然:
- 警告是有用的,因为这是很容易出错;
- 你可以有一个没有
main()
函数(库)的源文件。
编辑
一些参考:
-
main
不是保留字(C ++ 11):函数
main
不能在程序中使用。main
的连接(3.5)是实现定义的。 将main定义为已删除或将main声明为inline
,static
或constexpr
是不合格的。 名称main
不保留。 [例如:成员函数,类和枚举可以被称为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)的字符保留给实现用于任何用途。
- 以下划线开头的每个名字都保留给实现,以用作全局名称空间中的名称。
-
编程语言中的保留字 。
保留字可能不会被程序员重新定义,但是预定义的字通常可以被某种程度的覆盖。 这是
main
的情况:使用该标识符的声明重新定义其含义的范围。
是int main;
一个有效的C / C ++程序?
C / C ++程序是什么并不完全清楚。
是int main;
一个有效的C程序?
是。 独立实施允许接受这样的计划。 在独立的环境中main
没有什么特别的意义。
它在托pipe环境中无效。
是int main;
一个有效的C ++程序?
同上。
为什么会崩溃?
该程序不必在你的环境中有意义。 在一个独立的环境中,程序的启动和终止以及main
的含义都是由实现定义的。
为什么编译器会警告我?
只要不拒绝符合要求的程序,编译器可能会随时提出警告。 另一方面,警告是诊断不合格项目所需要的。 由于该翻译单元不能成为有效的托pipe程序的一部分,因此诊断消息是合理的。
gcc
是一个独立的环境,还是一个托pipe环境?
是。
-ffreestanding
文件编译标志。 添加它,警告消失。 你可能想在构build内核或固件时使用它。
g++
不logging这样的标志。 提供这个程序似乎没有影响。 假设由g ++提供的环境可能是安全的。 在这种情况下缺乏诊断是一个错误。
这是一个警告,因为它不是技术上不允许的。 启动代码将使用“main”的符号位置并使用三个标准参数(argc,argv和envp)跳转到该位置。 它没有,并在链接时无法检查它实际上是一个函数,甚至没有这些论点。 这也是为什么int main(int argc,char ** argv)的作用 – 编译器不知道envp参数,它只是不被使用,而且是调用者清理。
作为一个笑话,你可以做类似的事情
int main = 0xCBCBCBCB;
在x86机器上,忽略警告和类似的东西,它不会只是编译,但实际上也工作。
有人使用类似于这个的技术来编写一个可执行文件(可以直接运行在多个体系结构上) – http://phrack.org/issues/57/17.html#article 。 它也被用来赢得IOCCC – http://www.ioccc.org/1984/mullender/mullender.c 。
这是一个有效的程序吗?
没有。
它不是一个程序,因为它没有可执行的部分。
编译有效吗?
是。
它可以用于有效的程序吗?
是。
并非所有的编译代码都需要被执行才能生效。 示例是静态和dynamic库。
您已经有效地构build了一个对象文件。 这不是一个有效的可执行文件,但是另一个程序可以通过在运行时加载它来链接到生成文件中的对象main
。
这应该是一个错误?
传统上,C ++允许用户做一些看似没有用处但符合语言语法的东西。
我的意思是肯定的,这可能会被重新归类为一个错误,但为什么? 这个警告的目的是什么?
只要在实际代码中使用此function的理论可能性,具有称为main
的非函数对象就不太可能导致根据该语言的错误。
我想通过引用实际的语言标准来补充已经给出的答案。
是'int main' 一个有效的C程序?
简短的回答(我的意见):只有当你的实施使用“独立执行环境”。
以下所有来自C11的报价
5.环境
一个实现翻译C源文件,并在两个数据处理系统环境中执行C程序 ,这将被称为翻译环境和执行环境[…]
5.1.2执行环境
定义了两个执行环境:独立式和托pipe式。 在这两种情况下,当执行环境调用指定的C函数时都会发生程序启动。
5.1.2.1独立环境
在一个独立的环境中(C程序的执行可能没有任何操作系统的好处),程序启动时调用的函数的名称和types是实现定义的。
5.1.2.2托pipe环境
托pipe环境不需要提供,但应符合以下规范(如果存在)。
5.1.2.2.1程序启动
程序启动时调用的函数名为main 。 […]应使用inttypes的返回types,并且不带任何参数或者带有两个参数或者等价物,或者以某种其他实现定义的方式定义。
从这些,观察以下:
- C11程序可以有一个独立的或托pipe的执行环境,并且是有效的。
- 如果它具有独立的function,则不必存在主要function。
- 否则,必须有一个inttypes的返回值。
在独立的执行环境中,我会认为这是一个不允许启动的有效程序,因为没有5.1.2所要求的function。 在托pipe的执行环境中,当你的代码引入一个名为main的对象时,它不能提供返回值,所以我认为这不是一个有效的程序,虽然也可以像以前一样争辩说,如果程序不是意味着要被执行(例如,可能只想提供数据),那么它只是不能做到这一点。
是'int main' 一个有效的C ++程序?
简短的回答(我的意见):只有当你的实施使用“独立执行环境”。
从C ++引用14
3.6.1主要function
程序应该包含一个名为main的全局函数,它是程序的指定开始。 是否需要在独立环境中的程序来定义主要function是实施定义的。 它应该有一个types为int的返回types,否则它的types是实现定义的。 […]名称主不是保留。
在这里,与C11标准相比,对独立执行环境的限制较less,因为根本没有提到启动函数,而对于托pipe执行环境,情况几乎与C11相同。
再次,我会争辩说,对于托pipe的情况下,你的代码是不是一个有效的C + + 14程序,但我相信这是为独立的情况下。
由于我的回答只考虑执行环境,我认为由dasblinkenlicht的答案发挥作用,因为事先发生在翻译环境中发生的名称mangling。 在这里,我不太确定上面的引用是如此的严格。
我想我的观点是,我真的认为这应该是一个托pipe环境中的错误,呃?
错误是你的。 你没有指定一个名为main
的函数返回一个int
并试图在托pipe环境中使用你的程序。
假设你有一个定义了一个名为main
的全局variables的编译单元。 这在独立的环境中可能是合法的,因为什么构成一个程序是在独立的环境中执行的。
假设你有另一个编译单元,它定义了一个名为main
的全局函数,它返回一个int
并且不带任何参数。 这正是托pipe环境中的程序所需要的。
如果只在独立环境中使用第一个编译单元,并且只在托pipe环境中使用第二个编译单元,那么一切都很好。 如果你在一个程序中同时使用,会怎样? 在C ++中,您违反了一个定义规则。 这是未定义的行为。 在C中,您违反了规则,即规定所有对单个符号的引用必须一致。 如果他们不是它的未定义的行为。 未定义的行为是“摆脱监狱,免费!” 卡给开发者的一个实现。 响应未定义行为的任何实现都符合标准。 该实现不必警告,更不用说检测,未定义的行为。
如果只使用这些编译单元中的一个,但是使用了错误的(这是你所做的)呢? 在C中,情况是明确的。 未能定义托pipe环境中两种标准格式之一的main
函数是未定义的行为。 假设你根本没有定义main
。 编译器/链接器没有关于这个错误的事情。 他们的抱怨对他们来说是好的。 C程序编译和链接没有错误是你的错,而不是编译器。
在C ++中有点不那么清楚,因为在托pipe环境中定义main
函数的失败是一个错误而不是未定义的行为(换句话说,它必须被诊断)。 然而,C ++中的一个定义规则意味着链接器可能相当愚蠢。 链接器的工作是parsing外部引用,并且由于一个定义规则,链接器不必知道这些符号是什么意思。 您提供了一个名为main
的符号,链接器期望看到一个名为main
的符号,所以就链接器而言,所有的都是好的。
到目前为止,C是实现定义的行为。
正如ISO / IEC9899所说:
5.1.2.2.1程序启动
1程序启动时调用的函数名为main。 这个实现声明了这个函数没有原型。 它应该用int的返回types来定义,并且不带参数:
int main(void) { /* ... */ }
或者带有两个参数(这里称为argc和argv,尽pipe可以使用任何名称,因为它们是声明它们的函数的本地):
int main(int argc, char *argv[]) { /* ... */ }
或同等学历; 或者以某种其他实现定义的方式。
不,这不是一个有效的程序。
对于C ++来说,最近明确地通过缺陷报告1886:main()的语言连接明确地形成了错误:
对main()来说,显然没有任何限制,但是它可能是格式不对或有条件支持的。
部分决议包括以下改变:
在全局范围声明一个variablesmain的程序,或者用C语言连接(在任何名字空间中)声明名字main的程序是不合格的。
我们可以在最新的C ++草案标准N4527(这是C ++ 1z草案)中find这个措辞。
clang和gcc的最新版本现在使这个错误( 看它现场 ):
error: main cannot be declared as global variable int main; ^
在这个缺陷报告之前,这是未定义的行为,不需要诊断。 另一方面,格式不正确的代码需要诊断,编译器可以将其作为警告或错误。