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

Is ‘int main;’ a valid C/C++ program?

我问是因为我的编译器似乎这么认为,尽管我不这么认为。

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++? 上的(优秀)答案并快速浏览语言规范,对我来说 肯定 function 是必需的。但是来自 gcc -Wmain 的措辞('main' 是 通常 一个函数)(这里没有错误)似乎可能否则建议。

但是为什么呢?这是否有一些奇怪的边缘案例或“历史”用途?有人知道是什么吗?

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

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

当然:

  • 警告很有用,因为这很容易出错;
  • 您可以拥有一个没有 main() 函数(库)的源文件。

编辑

部分参考资料:

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

    The function main shall not be used within a program. The linkage (3.5) of main is implementation-defined. A program that defines main as deleted or that declares main to be inline, static, or constexpr is ill-formed. The name main is not otherwise reserved. [ Example: member functions, classes and enumerations can be called main, as can entities in other namespaces. — end example ]

    C++11 - [basic.start.main] 3.6.1.3

    [2.11/3] [...] some identifiers are reserved for use by C++ implementations and standard libraries (17.6.4.3.2) and shall not be used otherwise; no diagnostic is required.

    [17.6.4.3.2/1] Certain sets of names and function signatures are always reserved to the implementation:

    • Each name that contains a double underscore __ or begins with an underscore followed by an uppercase letter (2.12) is reserved to the implementation for any use.
    • Each name that begins with an underscore is reserved to the implementation for use as a name in the global namespace.
  • Reserved words in programming languages.

    保留字可能不会被程序员重新定义,但预定义通常可以在某些方面被覆盖。这是 main 的情况:在某些范围内,使用该标识符的声明会重新定义其含义。

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

  • C++ 使用名称修饰来帮助 linker 区分不同类型的文本相同符号,例如一个全局变量 xyz 和一个独立的全局函数 xyz(int)。但是,名称 main 永远不会被破坏。
  • C 不使用 mangling,因此程序有可能通过提供一种符号代替不同的符号来混淆 linker,并使程序成功 link .

这就是这里发生的事情:linker 希望找到符号 main,它确实找到了。 "wires" 那个符号就好像它是一个函数一样,因为它不知道有什么更好的。 运行time 库中将控制权交给 main 的部分要求 linker 提供 main,因此 linker 给它符号 main,让link 阶段完成。当然这在 运行 时失败了,因为 main 不是函数。

这是同一问题的另一个例子:

文件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

这会编译,它可能会 运行,但它是未定义的行为,因为承诺给编译器的符号类型与提供给 linker 的实际符号不同。

就警告而言,我认为这是合理的:C 允许您构建没有 main 函数的库,因此编译器释放名称 main 用于其他用途,如果您由于某些未知原因需要定义变量 main

这是一个警告,因为它在技术上并没有被禁止。启动代码将使用 "main" 的符号位置,并使用三个标准参数(argc、argv 和 envp)跳转到该位置。它没有,并且在 link 时间无法检查它实际上是一个函数,甚至无法检查它是否具有这些参数。这也是 int main(int argc, char **argv) 起作用的原因——编译器不知道 envp 参数,它恰好没有被使用,它是调用者清理。

开个玩笑,你可以这样做

int main = 0xCBCBCBCB;

在 x86 机器上,忽略警告和类似的东西,它不仅会编译而且实际上也能工作。

有人使用与此类似的技术来编写可直接在多种体系结构上运行的可执行文件(某种程度上)- http://phrack.org/issues/57/17.html#article . It was also used to win the IOCCC - http://www.ioccc.org/1984/mullender/mullender.c .

程序有效吗?

没有

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

编译有效吗?

是。

能否与有效程序一起使用?

是。

并非所有编译后的代码都必须是可执行文件才有效。例如静态库和动态库。

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

这应该是一个错误吗?

传统上,C++ 允许用户做一些看似无用但符合语言语法的事情。

我的意思是,当然,这可以重新归类为错误,但为什么呢?警告没有起到什么作用?

只要在理论上有可能在实际代码中使用此功能,根据语言,拥有一个名为 main 的非函数对象就不太可能导致错误。

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

不完全清楚什么是 C/C++ 程序。

int main; 是一个有效的 C 程序吗?

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

在托管环境中无效

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

同上。

为什么会崩溃?

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

为什么编译器会警告我?

编译器可以随心所欲地警告您,只要它不拒绝符合要求的程序。另一方面,警告是诊断不合格程序所需要的全部。由于此翻译单元不能是有效托管程序的一部分,因此诊断消息是合理的。

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

是的。

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

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

My point, I suppose, is that I really think this should be an error in a hosted environment, eh?

错误是你的。您没有指定名为 main 且 returns 为 int 的函数,并试图在托管环境中使用您的程序。

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

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

如果您只在独立环境中使用第一个编译单元而在托管环境中只使用第二个编译单元,一切都很好。如果您在一个程序中同时使用两者怎么办?在 C++ 中,您违反了单一定义规则。那是未定义的行为。在 C 语言中,您违反了所有对单个符号的引用必须一致的规则;如果不是,则为未定义行为。未定义的行为是实现开发人员的一张 "get out of jail, free!" 卡片。实现对未定义行为所做的任何响应都符合标准。该实现不必警告,更不用说检测未定义的行为了。

如果你只使用其中一个编译单元,但你用错了(你就是这么做的)怎么办?在C中,情况一目了然。未能在托管环境中以两种标准形式之一定义函数 main 是未定义的行为。假设您根本没有定义 main。 compiler/linker 并没有对这个错误置之不理。他们确实抱怨代表他们的好处。 C 程序编译和链接没有错误是你的错,而不是编译器的错。

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

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

‘int main;’是一个有效的 C 程序吗?

简短回答(我的意见):仅当您的实施使用 "freestanding execution environment".

以下所有引用自C11

5.环境

An implementation translates C source files and executes C programs in two dataprocessing-system environments, which will be called the translation environment and the execution environment [...]

5.1.2 执行环境

Two execution environments are defined: freestanding and hosted. In both cases, program startup occurs when a designated C function is called by the execution environment.

5.1.2.1 独立环境

In a freestanding environment (in which C program execution may take place without any benefit of an operating system), the name and type of the function called at program startup are implementation-defined.

5.1.2.2 托管环境

A hosted environment need not be provided, but shall conform to the following specifications if present.

5.1.2.2.1程序启动

The function called at program startup is named main. [...] It shall be defined with a return type of int and with no parameters [...] or with two parameters [...] or equivalent or in some other implementation-defined manner.

从中观察到以下几点:

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

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

‘int main;’是一个有效的 C++ 程序吗?

简短回答(我的意见):仅当您的实施使用 "freestanding execution environment".

引自C++14

3.6.1 主要功能

A program shall contain a global function called main, which is the designated start of the program. It is implementation-defined whether a program in a freestanding environment is required to define a main function. [...] It shall have a return type of type int, but otherwise its type is implementation-defined. [...] The name main is not otherwise reserved.

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

同样,我认为对于托管案例,您的代码不是有效的 C++14 程序,但我确信它适用于独立案例。

由于我的回答只考虑了 execution 环境,我认为 dasblinkenlicht 的回答起作用了,因为在 translation 环境事先发生。在这里,我不太确定上面的引述是否被严格遵守。

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

正如 ISO/IEC9899 所说:

5.1.2.2.1 Program startup

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.

不,这不是一个有效的程序。

对于 C++,这最近被 defect report 1886: Language linkage for main() 明确定义为格式错误,它说:

There does not appear to be any restriction on giving main() an explicit language linkage, but it should probably be either ill-formed or conditionally-supported.

部分决议包括以下更改:

A program that declares a variable main at global scope or that declares the name main with C language linkage (in any namespace) is ill-formed.

我们可以在最新的 C++ draft standard N4527 C++1z 草案中找到这个措辞。

最新版本的 clang 和 gcc 现在都会出现此错误 (see it live):

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

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