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 函数。事实上,你根本不能调用main1,所以在任何事情之前都不需要声明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;

如果您在一个文件中使用声明来使编译器在定义函数之前知道它,那么它的定义必须在链接时知道:

ma​​in.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++ 枚举类型声明可以放在 int main 的前面吗?

C/C++ 中 void main 和 int main 的区别? [复制]

c++ 中如何声明一个 int 变量

请问C++中如果需要中途退出程序,exit(1)和exit(0)以及return有啥区别呢? 谢谢!

请问在C程序中啥时候用int main() 啥时候用 void main()。二者是不是可以相互代替?

初学C++有些字符我怎么输出的黑字而不是蓝色的字或者红色的比如int ‘main 我怎么输入的是黑字而不是红字