是“ int main;” 有效的C / C ++程序?


113

我问是因为我的编译器似乎是这样认为的,即使我没有。

echo 'int main;' | cc -x c - -Wall
echo 'int main;' | c++ -x c++ - -Wall

Clang对此不发出警告或错误,并且gcc仅发出温和的警告:'main' is usually a function [-Wmain],但仅当编译为C时发出。指定a -std=似乎无关紧要。

否则,它将编译和链接正常。但是在执行时,它立即终止SIGBUS(对我来说)。

通读以下内容的(优秀)答案:main()在C和C ++中应该返回什么?以及通过语言规范的快速grep,在我看来,肯定需要一个主要功能。但是gcc -Wmain('main' 通常是一个函数)的废话(以及此处缺少错误)似乎暗示了其他方面。

但为什么?是否有一些奇怪的极端情况或“历史性”用法?有人知道会给什么吗?

我的意思是,我真的认为这在托管环境中应该是一个错误,是吗?


6
要使gcc成为(主要是)标准兼容的编译器,您需要gcc -std=c99 -pedantic ...
pmg 2015年

3
@pmg相同的警告,有或没有-pedantic或有任何-std。我的系统c99也编译此文件而没有警告或错误...
Geoff Nixon

3
不幸的是,如果您“足够聪明”,则可以创建编译器可以接受但没有意义的内容。在这种情况下,您要链接C运行时库以调用名为的变量main,这不太可能起作用。如果使用“正确的”值初始化main,则实际上可能会返回...
Mats Petersson 2015年

7
即使它是有效的,它也是一件可怕的事情(无法读取的代码)。顺便说一句,托管实施和独立实施(可能不知道main)可能有所不同
Basile Starynkevitch 2015年

1
要获得更多的欢乐时光,请尝试main=195;
imallett 2015年

Answers:


97

由于该问题被双重标记为C和C ++,因此C ++和C的理由将有所不同:

  • C ++使用名称修饰来帮助链接程序区分不同类型的文本相同符号,例如,全局变量xyz和独立的全局函数xyz(int)。但是,该名称main从未被篡改。
  • C不使用修饰,因此程序有可能通过提供一种符号来代替其他符号而使链接器混乱,并使程序成功链接。

这就是这里发生的事情:链接器希望找到symbol main,并且确实可以。它像“函数”一样“连接”该符号,因为它并不更好。运行时库中将控制权传递给main链接器的部分,向链接器询问main,因此链接器将其赋予symbol 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 x.c y.c

这样可以编译,并且可能会运行,但这是未定义的行为,因为提供给编译器的符号类型与提供给链接器的实际符号不同。

就警告而言,我认为这是合理的:C使您可以构建不具有任何main功能的库,因此,main如果main出于某些未知原因需要定义变量,则编译器会释放该名称以供其他用途。


3
虽然,C ++编译器对主要函数的处理不同。即使没有外部“ C”,其名称也不会被篡改。我猜这是因为否则它将需要发出它自己的外部“ C” main来确保链接。
UldisK 2015年

@UldisK是的,我本人注意到了这一点,发现很有趣。这是有道理的,但我从未想过。
杰夫·尼克松

2
实际上,如此处所指出的,C ++和C的结果没有区别- main不论它是否是函数,都不受C ++中名称的处理(看来)。
杰夫·尼克松

4
@nm我认为您对问题的解释太狭::除了在帖子标题中询问问题外,OP显然还寻求解释其程序为何首先编译的原因(“我的编译器似乎是这样认为的,即使我没有”),也建议为什么将其定义main为除函数之外的任何内容都可能有用。答案为这两个部分提供了解释。
dasblinkenlight 2015年

1
符号main不受名称修饰的影响是无关紧要的。在C ++标准中没有提及名称修饰。名称处理是一个实现问题。
David Hammen,2015年

30

main是不是保留字,它只是一个预定义的标识符(如cinendlnpos...),所以你可以声明一个变量叫main,初始化它,然后打印出它的价值。

当然:

  • 警告很有用,因为这很容易出错;
  • 您可以有一个不带main()功能的源文件(库)。

编辑

一些参考:

  • main 不是保留字(C ++ 11):

    该功能main不得在程序内使用。的链接(3.5)由main实现定义。为删除或声明主限定主一种程序,是inlinestatic,或者 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:在某些范围内,使用该标识符的声明会重新定义其含义。


-我想我很容易被以下事实困扰:(因为它容易出错),为什么这是一个警告(不是错误),以及为什么当编译为C时它只是一个警告-当然,您可以在不进行编译的情况下进行编译一个main()功能,但你不能链接它作为一个程序。这里发生的是,一个“有效”程序被链接时没有main(),而只是一个main
杰夫·尼克松

7
cin并且endl不在默认名称空间中-它们在std名称空间中。npos是的成员std::basic_string
AnotherParker

1
main 保留作为全局名称。您提到的其他所有内容或main均未预定义。
Potatoswatter

1
有关main允许的限制,请参见C ++ 14§3.6.1和C11§5.1.2.2.1 。C ++表示“实现不得预定义主函数”,C表示“实现不声明此函数的原型”。
Potatoswatter 2015年

@manlio:请澄清您引用的内容。至于纯C,这些引用是错误的。所以我想这是任何c ++标准不是吗?
dhein

19

int main;有效的C / C ++程序吗?

目前尚不清楚什么是C / C ++程序。

int main;有效的C程序吗?

是。一个独立的实现被允许接受这样的程序。main在独立的环境中不必具有任何特殊含义。

这是不是在托管环境中有效。

int main;有效的C ++程序吗?

同上。

为什么会崩溃?

该程序在您的环境中不一定有意义。在独立的环境中,程序的启动和终止以及的含义main是实现定义的。

为什么编译器会警告我?

只要不拒绝符合要求的程序,编译器就可能会警告您任何令人愉悦的事情。另一方面,警告是诊断不合格程序所需要的。由于此翻译单元不能成为有效托管程序的一部分,因此有必要提供诊断消息。

gcc独立的环境,还是托管环境?

是。

gcc记录-ffreestanding编译标志。添加它,警告消失。您可能需要在构建内核或固件时使用它。

g++没有记录这样的标志。提供它似乎对该程序没有影响。可以安全地假设由g ++提供的环境是托管的。在这种情况下,缺乏诊断是一个错误。


17

这是警告,因为从技术上讲这是不允许的。启动代码将使用“ 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


1
“这是一个警告,因为在技术上不禁止这样做” –在C ++中无效。
干杯和健康。-Alf 2015年

3
“三个标准参数(argc,argv和envp)”-在这里您可能正在谈论Posix标准。
干杯和健康。-Alf 2015年

在我的系统(Ubuntu 14 / x64)上,以下行适用于gcc:int main __attribute__ ((section (".text")))= 0xC3C3C3C3;
csharpfolk 2015年

@ Cheersandhth.-Alf前两个是标准,第三个是POSIX。
dascandy 2015年

9

它是有效程序吗?

没有。

它不是程序,因为它没有可执行部分。

编译有效吗?

是。

可以与有效程序一起使用吗?

是。

并非所有已编译的代码都必须可执行才能有效。示例是静态和动态库。

您已经有效地构建了一个目标文件。它不是有效的可执行文件,但是另一个程序可以main通过在运行时加载结果文件来链接到结果文件中的对象。

这应该是一个错误吗?

传统上,C ++允许用户执行似乎没有有效使用但符合该语言语法的操作。

我的意思是,可以将其重新分类为错误,但是为什么呢?警告没有用的目的是什么?

只要理论上有可能在实际代码中使用此功能,main根据语言,调用非功能对象就不太可能导致错误。


它创建一个名为的外部可见符号main。一个有效的程序(该程序必须具有一个名为的外部可见函数main)如何链接到该程序?
基思·汤普森

@KeithThompson在运行时加载。将澄清。
Michael Gazonda

可以这样做是因为它无法区分符号类型之间的区别。链接工作得很好-执行(在精心设计的情况下除外)不行。
克里斯·斯特拉顿

1
@ChrisStratton:我认为Keith的观点是链接失败,因为符号被多次定义...因为“有效程序”除非定义了main函数,否则将不是有效程序。
Ben Voigt 2015年

@BenVoigt但是,如果它出现在库中,则链接不会(也可能不会)失败,因为在程序链接时,该int main;定义将不可见。

6

我想通过引用实际语言标准来补充已经给出的答案。

是“ int main;” 有效的C程序?

简短答案(我认为):仅当您的实现使用“独立执行环境”时。

以下所有来自C11的报价

5.环境

一种实现在两个数据处理系统环境中转换C源文件并执行C程序,这些环境称为转换环境和执行环境[...]

5.1.2执行环境

定义了两个执行环境:独立和托管。在这两种情况下,当执行环境调用指定的C函数时,都会发生程序启动。

5.1.2.1独立环境

在独立的环境中(可能在没有操作系统的任何好处的情况下执行C程序),程序启动时调用的函数的名称和类型是实现定义的。

5.1.2.2托管环境

不需要提供托管环境,但应符合以下规范(如果存在)。

5.1.2.2.1程序启动

程序启动时调用的函数名为main。应当使用返回类型int来定义它,并且不使用任何参数,或者使用两个参数或等效参数,或者以其他实现定义的方式来定义它。

从这些观察到以下内容:

  • C11程序可以具有独立的或托管的执行环境,并且有效。
  • 如果它具有独立的功能,则不必存在主要功能。
  • 否则,必须存在一个带有int类型的返回值的值。

在独立的执行环境中,我认为这是一个不允许启动的有效程序,因为没有5.1.2中要求的功能。在托管执行环境中,虽然您的代码引入了一个名为main的对象,但是它无法提供返回值,因此我认为这不是一个有效的程序,尽管如果程序不是这样,则也可以像以前那样争论意味着要执行(例如,可能只想提供数据),那么就不允许这样做。

是“ int main;” 有效的C ++程序?

简短答案(我认为):仅当您的实现使用“独立执行环境”时。

引用C ++ 14

3.6.1主要功能

程序应包含一个称为main的全局功能,这是程序的指定启动位置。是否需要独立环境中的程序来定义主要功能由实现定义。[...]它的返回类型应为int类型,否则它的类型是实现定义的。[...] main名称没有其他保留。

在这里,与C11标准相反,对独立执行环境的限制较少,因为根本没有提及启动功能,而对于托管执行环境,情况与C11几乎相同。

再次,我认为对于托管案例,您的代码不是有效的C ++ 14程序,但我确定它是针对独立案例的。

由于我的答案仅考虑执行环境,因此我认为dasblinkenlicht的答案已发挥作用,因为翻译环境中发生的名称修改是事先发生的。在这里,我不确定上述引号是否被严格遵守。


4

我的意思是,我真的认为这在托管环境中应该是一个错误,是吗?

错误是你的。您未指定名为的函数main返回int并试图在托管环境中使用您的程序。

假设您有一个定义了名为的全局变量的编译单元main。在独立环境中,这很可能是合法的,因为程序的组成要由独立环境中的实现决定。

假设您有另一个编译单元,它定义一个名为的全局函数main,该函数返回an int并且不接受任何参数。这正是托管环境中的程序所需要的。

如果仅在独立环境中使用第一个编译单元,而在托管环境中仅使用第二个编译单元,那么一切都很好。如果您在一个程序中同时使用两者,该怎么办?在C ++中,您违反了一个定义规则。那是未定义的行为。在C语言中,您违反了以下规则:该规则要求对单个符号的所有引用必须一致。如果不是,那是未定义的行为。未定义的行为是“免费出狱!” 发给开发人员的实现。实现对未定义行为的响应所做的任何事情都符合该标准。该实现不必警告(更不用说检测未定义的行为)了。

如果您仅使用其中一个编译单元,但使用了错误的编译单元(即您所做的)怎么办?在C语言中,情况很明显。无法main在托管环境中以两种标准形式之一定义功能是未定义的行为。假设您根本没有定义main。编译器/链接器不需要说关于此错误的内容。他们确实抱怨代表他们。C程序编译和链接没有错误是您的错,而不是编译器的错。

在C ++中不太清楚,因为main在托管环境中未能定义函数是错误而不是未定义的行为(换句话说,必须对其进行诊断)。但是,C ++中的一个定义规则意味着链接器可能很笨。链接器的工作是解析外部引用,并且由于有一个定义规则,链接器不必知道这些符号的含义。您提供了一个名为的符号main,链接器希望看到一个名为的符号main,因此就链接器而言,一切都很好。


4

到目前为止,对于C来说,这是实现定义的行为。

正如ISO / IEC9899所说:

5.1.2.2.1程序启动

1在程序启动时调用的函数称为main。该实现没有为此函数声明任何原型。它应使用返回类型int且不带参数来定义:

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

或带有两个参数(尽管可以使用任何名称,但在此处称为argc和argv,因为它们是声明它们的函数的局部名称):

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

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


3

不,这不是有效的程序。

对于C ++,最近由于缺陷报告1886:main()的语言链接而明确指出,这种形式是错误的:

给main()一个明确的语言链接似乎没有任何限制,但是它应该是格式错误的或有条件的支持。

该决议的一部分包括以下更改:

在全局范围内声明变量main或使用C语言链接(在任何命名空间中)声明main名称的程序格式错误。

我们可以在最新的C ++草案标准N4527(即C ++ 1z草案)中找到此措辞。

现在,最新版本的clang和gcc都将其变为错误(实时查看):

error: main cannot be declared as global variable
int main;
^

在此缺陷报告之前,它是未定义的行为,不需要诊断。另一方面,格式错误的代码需要诊断,编译器可以将其设置为警告或错误。


感谢更新!很高兴看到编译器诊断现在已解决了这一问题。但是,我必须说,我发现C ++标准困惑中的变化。(有关背景,请参见上面有关的名称修饰的注释main()。)我理解不允许main()具有显式链接规范的理由,但我理解强制main()具有C ++链接的理由。当然,该标准并没有直接解决如何处理ABI链接/名称更改,但是在实践中(例如,使用Itanium ABI)这将被更改main()_Z4mainv。我想念什么?
杰夫·尼克松

我认为超级猫的评论涵盖了这一点。如果实现是在调用用户定义的main之前做自己的事情,那么它可以轻松地选择调用错误的名称。
Shafik Yaghmour,2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.