main()方法在C中如何工作?

我知道有两个不同的签名来写主要的方法 –

int main() { //Code } 

或者处理命令行参数,我们把它写成 –

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

C++我知道我们可以重载一个方法,但是在C中,编译器如何处理main函数的这两个不同的签名?

C语言的一些function从刚开始工作的黑客开始。

main和variable-length参数列表的多个签名是这些特性之一。

程序员注意到他们可以传递额外的参数给一个函数,并且给定的编译器没有什么不好的事情发生。

如果调用约定是这样的,则是这种情况:

  1. 调用函数清理参数。
  2. 最左边的参数靠近堆栈的顶部,或者靠近堆栈的底部,这样伪参数不会使地址无效。

遵循这些规则的一组调用约定是基于堆栈的parameter passing,调用者popup参数,并从右向左推送:

  ;; pseudo-assembly-language ;; main(argc, argv, envp); call push envp ;; rightmost argument push argv ;; push argc ;; leftmost argument ends up on top of stack call main pop ;; caller cleans up pop pop 

在这种types的调用约定是这种情况的编译器中,没有什么特别的需要去支持这两种main ,甚至是另外的types。 main可以是一个没有参数的函数,在这种情况下,它不会被推入堆栈的项目。 如果它是两个参数的函数,那么它会将argcargv作为两个最上面的堆栈项目。 如果它是一个具有环境指针(一个公共扩展)的特定于平台的三参数变体,那么也可以工作:它将从堆栈顶部find第三个参数作为第三个元素。

因此,固定呼叫适用于所有情况,允许将一个固定的启动模块链接到该程序。 这个模块可以用C编写,类似这样的function:

 /* I'm adding envp to show that even a popular platform-specific variant can be handled. */ extern int main(int argc, char **argv, char **envp); void __start(void) { /* This is the real startup function for the executable. It performs a bunch of library initialization. */ /* ... */ /* And then: */ exit(main(argc_from_somewhere, argv_from_somewhere, envp_from_somewhere)); } 

换句话说,这个启动模块总是调用一个三参数的main。 如果main没有参数,或者只有int, char ** ,那么由于调用约定,它恰好工作正常,并且如果不带参数。

如果你要在你的程序中做这样的事情,这将是不可移植的,并被ISO C认为是未定义的行为:以一种方式声明和调用一个函数,并在另一个方法中定义它。 但编译器的启动技巧不必是可移植的; 它不受便携式程序规则的指导。

但是,假设调用约定是不能这样工作的。 在这种情况下,编译器必须专门处理main 。 当它注意到正在编译main函数时,它可以生成与三参数调用兼容的代码。

也就是说,你这样写:

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

但是当编译器看到它的时候,它本质上会执行一个代码转换,所以它编译的function看起来更像这样:

 int main(int __argc_ignore, char **__argv_ignore, char **__envp_ignore) { /* ... */ } 

除了名称__argc_ignore不是字面上存在的。 没有这样的名字被引入你的范围,并且不会有任何关于未使用的参数的警告。 代码转换使编译器发出具有正确链接的代码,知道它必须清理三个参数。

另一个实现策略是让编译器或链接器自定义生成__start函数(或者被调用的函数),或者至less从几个预编译的选项中select一个。 信息可以存储在目标文件中,关于正在使用哪个main的支持forms。 链接器可以查看这个信息,并select正确版本的启动模块,其中包含一个与程序定义兼容的main调用。 C实现通常只有less量的main支持forms,所以这种方法是可行的。

对于C99语言的编译器总是必须特别对待main ,在一定程度上支持如果函数在没有return语句的情况下终止,行为就好像return 0被执行一样。 这又可以通过代码转换来处理。 编译器注意到正在编译一个名为main的函数。 然后它检查身体的末端是否有可能到达。 如果是这样,它插入一个return 0;

即使在C ++中也没有main重载。 主要function是程序的入口点,只有一个定义应该存在。

对于标准C

对于托pipe环境(这是正常的),C99标准说:

5.1.2.2.1程序启动

程序启动时调用的函数名为main 。 这个实现声明了这个函数没有原型。 它应该用int的返回types来定义,并且不带参数:

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

或者带有两个参数(这里称为argcargv ,尽pipe可以使用任何名称,因为它们是声明它们的函数的本地):

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

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

9)因此, int可以被定义为int的typedef名称replace,或者argv的types可以被写为char **argv ,依此类推。

对于标准的C ++:

3.6.1主要function[basic.start.main]

1程序应该包含一个名为main的全局函数,它是程序的指定开始。 […]

2实现不应该预定义主函数。 该function不得超载 。 它应该有一个types为int的返回types,否则其types是实现定义的。 所有的实现都应该允许main的以下两个定义:

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

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

C ++标准明确地说“它的主函数应该有一个types为int的返回types,否则其types是实现定义的”,并且需要与C标准相同的两个签名。

托pipe环境 (也支持C库的AC环境)中 – 操作系统调用main

非托pipe环境中 (一个用于embedded式应用程序),您可以随时使用预处理器指令来更改程序的入口点(或退出点)

 #pragma startup [priority] #pragma exit [priority] 

优先级是可选的整数。

在main(priority-wise)和pragma出口在main函数之后执行函数之前,Pragma启动执行该函数。 如果有多个启动指令,则优先级决定首先执行哪个指令。

没有必要超载。 是的,有两个版本,但是当时只能使用一个版本。

这是C和C ++语言的奇怪的不对称和特殊规则之一。

在我看来,这只是出于历史原因而存在的,没有真正的严肃逻辑。 请注意, main也是由于其他原因(例如C ++中的main不能recursion,你不能接受它的地址,并且在C99 / C ++中允许你省略最终的return语句)。

还要注意,即使在C ++中,它也不是一个重载…程序有第一种forms,或者有第二种forms; 它不能兼得。

main的不寻常之处不在于它可以用多种方式来定义,而是它只能以两种不同的方式来定义。

main是一个用户定义的函数; 该实现不会为其声明原型。

foobar也是如此,但是你可以用你喜欢的方式来定义这些名字的函数。

不同之处在于main是由实现(运行时环境)调用的,而不仅仅是你自己的代码。 实现并不局限于普通的C函数调用语义,所以它可以(也必须)处理一些变化 – 但是并不需要处理无限多的可能性。 int main(int argc, char *argv[])forms允许使用命令行参数,C语言中的int main(void)或C ++中的int main()对于不需要处理的简单程序命令行参数。

至于编译器如何处理这个,取决于实现。 大多数系统可能具有调用约定,这两种forms可以有效地兼容,任何传递给不带参数的main函数的参数都会被忽略。 如果没有,编译器或链接器就不会特别对待main 。 如果你很好奇它是如何在你的系统上运行的 ,你可以看看一些汇编列表。

就像C和C ++中的许多事情一样,细节主要是由语言devise者和他们的前辈做出的历史和任意决定的结果。

请注意,C和C ++都允许为main定义其他实现定义的定义,但很less有任何理由使用它们。 对于独立实现 (例如没有OS的embedded式系统),程序入口点是实现定义的,并不一定称为main

main是一个由链接器决定的起始地址的名称,其中main是默认名称。 程序中的所有函数名称都是函数启动的起始地址。

函数参数在堆栈上被压入/popup,所以如果没有为函数指定参数,那么没有参数被压入/popup堆栈。 这就是主要的可以有或没有论据。

那么,main()函数的两个不同的签名只有在你想要的时候才会出现,我的意思是如果你的程序在你的代码实际处理之前需要数据,你可以通过使用 –

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

其中variablesargc存储传递的数据的计数,argv是指向char的指针数组,指向从控制台传递的值。 否则,总是很好

  int main() { //Code } 

然而在任何情况下,程序中都只能有一个main(),因为这是程序开始执行的唯一的一点,因此它不能超过一个。 (希望它值得)

之前有人问过类似的问题: 为什么没有参数的函数(与实际函数定义相比)编译?

排名最高的答案之一是:

在C的func()意味着你可以传递任意数量的参数。 如果你不需要参数,那么你必须声明为func(void)

所以,我想这是如何宣布main (如果你可以申请“声明” main术语)。 其实你可以写这样的东西:

 int main(int only_one_argument) { // code } 

它仍然会编译和运行。

你不需要重写这个,因为一次只能使用一个。但是主函数有两个不同的版本