int main() 是不是需要在 C++ 上声明?
Posted
技术标签:
【中文标题】int main() 是不是需要在 C++ 上声明?【英文标题】:Does int main() need a declaration on C++?int main() 是否需要在 C++ 上声明? 【发布时间】:2019-08-22 18:31:38 【问题描述】:我被告知函数需要声明才能被调用。为了说明,下面的例子会给我一个错误,因为没有函数 sum
的声明:
#include <iostream>
int main()
std::cout << "The result is " << sum(1, 2);
return 0;
int sum(int x, int y)
return x + y;
// main.cpp:4:36: error: use of undeclared identifier 'sum'
// std::cout << "The result is " << sum(1, 2);
// ^
// 1 error generated.
为了解决这个问题,我会添加声明:
#include <iostream>
int sum(int x, int y); // declaration
int main()
std::cout << "The result is " << sum(1, 2);
return 0;
int sum(int x, int y)
return x + y;
为什么 main
函数不需要声明,而像 sum
这样的其他函数需要声明?
【问题讨论】:
手动调用 main 会调用未定义的行为。 @MichaelStachowsky -- 在 C 语言中,您可以调用main
。在 C++ 中你不是;它不仅仅是“一个功能”——它很特别。从历史上看,原因是编译器在main
中添加了代码来初始化需要动态初始化的全局变量;从程序内部调用main
将重新初始化这些变量,结果将是混乱。
@Michael 你已经尝试了一些东西并发现“它工作得很好”并不能证明某些东西不是未定义的行为。
顺便说一句,如果将 main 上方的定义放在文件中,则不需要声明 sum
。出于这个原因,通常将main
视为 C 和 C++ 源代码中的最后一个函数,因此您不需要对该文件中定义的其他函数进行前向声明。不像 C# 和 Java 通常将 main
放在首位,尽管在这些情况下不需要。
从技术上讲,您的示例代码已经声明了main
,函数的定义也声明了该函数。这就是为什么您可以将sum
移动到main
之前以避免必须单独声明sum
。
【参考方案1】:
函数的定义也是函数的声明。
声明函数的目的是让编译器知道它。声明一个函数而不定义它允许在不方便定义它的地方使用函数。例如:
如果在源文件 (A) 中使用了一个函数,而不是在 (B) 中定义的那个,我们需要在 A 中声明它(通常通过 A 包含的头文件,例如B.h
)。
如果两个或多个函数可以相互调用,那么我们不能在其他函数之前定义所有这些函数——其中一个必须是第一个。因此可以先提供声明,然后再提供定义。
许多人喜欢将“更高级别”的例程放在源文件的较早部分,然后将子例程放在后面。由于这些“更高级别”的例程调用各种子例程,因此必须更早地声明这些子例程。
在 C++ 中,用户程序从不调用main
,因此它从不需要在定义之前声明。 (请注意,如果您愿意,可以提供一个。在这方面,main
的声明没有什么特别之处。)在 C 中,程序可以调用 main
。在这种情况下,它确实要求在调用之前声明是可见的。
请注意,调用它的代码确实需要知道main
。这是通常称为 C++ 运行时启动代码的特殊代码。当您使用适当的链接器选项链接 C++ 程序时,链接器会自动为您包含该代码。无论代码是用什么语言编写的,它都有任何main
的声明,以便正确调用它。
【讨论】:
我认为这是迄今为止最完整和正确的答案。遗憾的是,由于文本丰富,它不会变得更受欢迎。您可以在开头添加一些 tl;dr 吗?另外,我认为,C++ 编译器以这种顺序方式解析代码可能并不明显。其他语言通过先扫描声明然后再扫描定义来克服这个问题。 C++ 仅在类体中克服了它。【参考方案2】:我被告知函数需要声明才能被调用。
的确如此。函数必须先声明,然后才能调用。
为什么我们不为
main
函数添加声明?
好吧,你没有调用main
函数。事实上,你根本不能调用main
1,所以在任何事情之前都不需要声明main
。
但从技术上讲,所有定义也是声明,因此您对 main
的定义也声明了 main
。
脚注 1:C++ 标准规定从程序内部调用 main
是未定义的行为。
这允许 C++ 实现将特殊的一次性启动代码放在 main 的顶部,如果它们无法从通常调用 main
的启动代码中的挂钩中使其更早运行的话。一些实际的实现实际上会这样做,例如调用一个快速数学函数,该函数设置一些 FPU 标志,例如 denormals-are-zero。
在一个假设的实现中,调用 main 可能会导致一些有趣的事情,例如为所有静态变量重新运行构造函数,重新初始化 new
/delete
使用的数据结构以跟踪分配,或其他完全破坏你的程序。或者它可能根本不会引起任何问题。未定义的行为并不意味着它必须在每个实现中都失败。
【讨论】:
【参考方案3】:如果您要调用该函数,则需要原型,但它尚不可用,如您的情况下的sum
。
你不能自己打电话给main
,所以不需要有原型。编写原型甚至是个坏主意。
【讨论】:
致电main
一点也不“坏主意”。 C允许它; C++ 未定义它的原因与它是一个坏主意无关。
@Kaz 做一些行为未定义的事情是个坏主意。
@eeroika 这是一个循环论证。定义明确的递归main
最先出现。答案说,你不仅不能这样做,而且它甚至是一个坏主意。这意味着它是一个坏主意,除了被禁止之外,还有其他原因,或者可能是因为它是一个坏主意而被禁止,这不是所以。这只是 C++ 方言未能实现的 C 的一个特性。
允许 C++ 编译器发出翻译后的图像 main
,就好像它是 extern "C"
链接一样。或者将其名称完全替换为不同的符号,例如__main
或其他。但是,在编译 main 时也可以忽略这些考虑,将其视为另一个函数,以便以普通方式声明 main
符号。对main
的递归调用可能期望调用一个名为main
的具有普通C++ 链接的C++ 函数,该函数支持重载等,但由于特殊处理,翻译中根本不需要这样的符号。
@MatthieuBrucher 啊,好的;我误读了。原型在 C++ 中没有任何用处。【参考方案4】:
不,编译器不需要main()
的前向声明。
main()
是 C++ 中的一个特殊函数。
关于 main() 需要记住的一些重要事项是:
-
链接器在创建可执行程序时要求存在一个且只有一个
main()
函数。
编译器需要以下两种形式之一的 main() 函数:
int main () /* body */
int main (int argc, char *argv[]) /* body */
其中body
是零个或多个语句
另一种可接受的形式是特定于实现的,并在调用函数时提供环境变量列表:
int main (int argc, char* argv[], char *envp[]) /* body */
编码员必须使用这些可接受的形式之一提供 main 的“定义”,但编码员不需要提供声明。编译器接受编码定义作为 main() 的声明。
-
如果没有提供 return 语句,编译器将提供
return 0;
作为函数体中的最后一条语句。
顺便说一句,C++ 程序是否可以调用 main() 有时会让人感到困惑。不建议这样做。 C++17 草案声明 main()“不得在程序中使用”。换句话说,不能从程序中调用。参见例如Working Draft Standard for C++ Programming Language, dated "2017-03-21", Paragraph 6.6.1.3, page 66。我意识到一些编译器支持这一点(包括我的),但下一版本的编译器可以修改或删除该行为,因为标准使用术语“不应”。
【讨论】:
另请注意,除了您在此处列出的两个之外,该标准还允许其他实现定义的 main 签名。一个常见的选项是添加包含环境变量的第三个参数(在 argv 之后)(使用与extern char** environ
相同的方法)
@SJL:当然!我只列出了编译器“必须”实现的那些。 environ
也很有帮助。
"编译器不需要声明main()
" 每个定义都是声明,所以我认为措辞需要调整。 “编译器将其声明为以下两个函数之一” 为什么“编译器声明”?我们总是自己为main
提供定义。
@HolyBlackCat:我明白你的意思。措辞很重要。即使我改变它,不引用整个标准,也不会有完整的答案。答案很简单。看看您对此更新的看法。
关于(前向)声明的 main 函数没有什么特别之处。这只是一个红鲱鱼。相反,我们不需要转发声明它,因为我们没有在定义之前引用它。就是这样。【参考方案5】:
从程序内部调用main
是非法的。这意味着唯一要调用它的是运行时,编译器/链接器可以处理设置。这意味着您不需要main
的原型。
【讨论】:
【参考方案6】:函数的定义也隐含地声明了它。如果你需要在定义之前引用一个函数,你需要在使用它之前声明它。
所以这样写也是有效的:
int sum(int x, int y)
return x + y;
int main()
std::cout << "The result is " << sum(1, 2);
return 0;
如果您在一个文件中使用声明来使编译器在定义函数之前知道它,那么它的定义必须在链接时知道:
main.cpp
int sum(int x, int y);
int main()
std::cout << "The result is " << sum(1, 2);
return 0;
sum.cpp
int sum(int x, int y)
return x + y;
或者sum
可能起源于一个库,所以你甚至不需要自己编译它。
main
函数不会在您的代码中的任何地方使用/引用,因此无需在任何地方添加main
的声明。
在您的main
函数之前和之后,c++ 库可能会执行一些初始化和清理步骤,并将调用您的main
函数。如果库的该部分将表示为 c++ 代码,那么它将包含 int main()
的声明,以便可以对其进行编译。该代码可能如下所示:
int main();
int __main()
__startup_runtime();
main();
__cleanup_runtime();
但是你又遇到了__main
的同样问题,所以在某些时候不再有 c++ 并且某个函数 (main
) 只是代表你的代码的入口点。
【讨论】:
C++ 使 UB 可以从程序内部调用main
,因此 C++ 编译器可以将这些启动/清理调用直接放入真正的 main
中(如果他们愿意)。此规则允许 C++ 编译器在 C 环境之上工作,例如,如果编译器无法使用其他机制,则为调用静态构造函数提供位置。 (编译器还必须将 main
识别为一个特殊的函数名称,以便给它一个隐含的 return 0
。)
@PeterCordes 从程序员的角度来看,由于标准,调用main
函数是UB。但是编译供应商或操作系统如何处理main
取决于实现。所以理论上main
的编译结果可能看起来像一个由运行时调用的常规函数,或者它不存在,正如你所说,编译器可以将这些启动/清理调用放在入口点在main
中显示的代码周围的应用程序。
是的,在大多数实现中,它只是一个普通函数(但带有隐式 extern "C"
以不对其进行 C++ 名称修改,因此无论函数签名如何,CRT 启动代码都可以链接到它) ,在 CRT 代码和/或动态链接器挂钩中完成真正的初始化工作。但就像我评论 Joshua 的回答一样,ICC(英特尔的编译器)实际上确实在 main
本身(godbolt.org/z/oWlmlc)中添加了一些启动代码,包括将 DAZ 和 FTZ 设置为禁用其默认值 -ffast-math
的子规范。 gcc/clang 链接不同的 CRT 启动文件以进行快速数学运算。【参考方案7】:
不。反正你不能叫它。
您只需要对在定义之前调用的函数进行前向声明。对于其他文件中定义的函数,您需要外部声明(看起来与前向声明完全一样)。
但是你不能在 C++ 中调用main
,所以你不需要一个。这是因为允许 C++ 编译器修改 main 来进行全局初始化。
[我查看了 crt0.c,它确实有一个 main 声明,但这既不是这里也不是那里]。
【讨论】:
您可以致电main
,这通常是不好的做法。
@CruzJean 不只是一种不好的做法,据我所知,它是未定义的行为
@CruzJean 不错的做法。调用它会调用未定义的行为。
@AlgirdasPreidžius 啊,我的立场是正确的。从来不知道。
这是因为C++编译器允许修改main进行全局初始化。是吗?我看不出它是如何工作的,就像你在main
中分配的那样,它可以改变程序的可观察效果。以上是关于int main() 是不是需要在 C++ 上声明?的主要内容,如果未能解决你的问题,请参考以下文章
C/C++ 中 void main 和 int main 的区别? [复制]
请问C++中如果需要中途退出程序,exit(1)和exit(0)以及return有啥区别呢? 谢谢!