我知道编写主方法有两种不同的签名形式 -
int main()
{
//Code
}
或者处理命令行参数,我们将其写成-
int main(int argc, char * argv[])
{
//code
}
我知道在C++
中我们可以重载一个方法,但在C
中编译器如何处理main
函数的这两个不同的签名?
C语言的一些特性最初只是一些 hack,碰巧起作用了。
多种 main 函数签名以及可变长度参数列表就是其中之一。
程序员们注意到他们可以向函数传递额外的参数,而使用其给定的编译器并不会出现什么问题。
前提是调用约定遵循以下规则:
遵守这些规则的一组调用约定是基于栈的参数传递方式,即调用者弹出参数,然后从右到左依次压入堆栈:
;; 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
在这种调用约定适用的编译器中,不需要特殊处理支持两种或多种main
的情况。如果main
是一个没有参数的函数,则它对被推入栈中的项目不予考虑。如果main
是一个有两个参数的函数,则它会将argc
和argv
作为栈顶的两个元素。如果main
是具有平台特定的三个参数的变体,并且带有环境指针(一种常见的扩展),那也没有问题:它将把第三个参数作为从栈顶开始的第三个元素。
因此,一个固定的调用可以适用于所有情况,允许将单个固定的启动模块链接到程序中。该模块可以是用C编写的函数,类似于以下形式:
/* 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)
{
/* ... */
}
但是当编译器看到它时,它基本上会执行代码转换,以便编译的函数看起来更像这样:
int main(int __argc_ignore, char **__argv_ignore, char **__envp_ignore)
{
/* ... */
}
除了名称__argc_ignore
并不存在,这些名称不会引入到您的作用域中,并且不会有关于未使用参数的警告。
代码转换使编译器生成正确链接的代码,该代码知道必须清理三个参数。
另一种实现策略是由编译器或链接器定制生成__start
函数(或其他名称),或者至少从几个预编译的选择中选择一个。可以将信息存储在目标文件中,指示正在使用哪个被支持的main
形式。链接器可以查看此信息,并选择包含调用与程序定义兼容的main
版本的启动模块的正确版本。C实现通常只有少量支持的main
形式,因此这种方法是可行的。
C99语言的编译器始终需要在某种程度上特殊处理main
,以支持如果函数在没有return
语句的情况下终止,则行为就像执行了return 0
一样的技巧。这也可以通过代码转换来处理。编译器注意到正在编译名为main
的函数。然后它检查函数体是否可能到达末尾。如果是,它插入return 0;
语句。
即使在C++中也不存在对main
的重载。Main函数是程序的入口点,应该只存在一个定义。
对于标准的C语言:
对于托管环境(通常情况下),C99标准规定:
5.1.2.2.1程序启动
在程序启动时调用的函数名为
main
。实现不为此函数声明原型。它应该被定义为返回类型为int
且没有参数:
int main(void) { /* ... */ }
或使用两个参数(这里称为
argc
和argv
,但可以使用任何名称,因为它们是在声明它们的函数中局部的):
int main(int argc, char *argv[]) { /* ... */ }
或等效; 9) 或以其他实现定义的方式。
9) 因此,
int
可以被定义为int
的 typedef 名称所替代,或者argv
的类型可以写成char ** argv
,等等。对于标准C++:
3.6.1 主函数 [basic.start.main]
1 程序必须包含一个名为 main 的全局函数,这是程序的指定起点。[...]
2 一个实现不得预定义主函数。 此函数不得被重载。它应具有返回类型 int,但其类型在实现中是定义的。 所有实现都应允许以下两种 main 的定义:
int main() { /* ... */ }
和
int main(int argc, char* argv[]) { /* ... */ }
根据C++标准,"main函数的返回类型必须是int类型,但其它方面它的类型是由实现定义的",并要求与C标准相同的两个签名。
在托管环境中(支持C库的C环境),操作系统调用
main
函数。在非托管环境中(用于嵌入式应用程序),您可以使用预处理器指令更改程序的入口点(或退出点)。
#pragma startup [priority] #pragma exit [priority]
priority是一个可选的整数。
Pragma startup在主函数之前(按优先级)执行该函数,而pragma exit在主函数之后执行该函数。如果有多个启动指令,则priority决定哪个指令首先执行。
不需要过载。是的,有两个版本,但一次只能使用一个。
main
函数的不寻常之处并不在于它可以以多种方式定义,而是它只能以两种不同的方式之一进行定义。
main
是用户定义的函数;实现没有为其声明原型。
foo
或bar
也是如此,但你可以按任何喜欢的方式定义这些函数。main
由实现(运行时环境)调用,而不仅仅是由你自己的代码调用。实现不局限于普通的C函数调用语义,因此它可以(并且必须)处理一些变化——但它不需要处理无限多种可能性。形如int main(int argc, char *argv[])
的形式允许使用命令行参数,而C的int main(void)
或C++的int main()
则只是用于简单程序不需要处理命令行参数的便利方式。main
的任何参数都会被悄悄地忽略掉。如果没有,编译器或链接器很容易特殊处理main
。如果你想知道它在你的系统上是如何工作的,可以看一些汇编清单。main
的其他实现定义——但很少有什么好理由使用它们。对于自由实现(例如没有操作系统的嵌入式系统),程序入口点是实现定义的,甚至不一定称为main
。这是C和C++语言中奇怪的不对称性和特殊规则之一。
我的观点是,它存在只是出于历史原因,并没有真正严肃的逻辑支持。请注意,main
还有其他特殊原因(例如,在C++中,main
不能递归,您不能获取其地址,在C99 / C ++中允许省略最后的return
语句)。
同时注意,即使在C++中,它也不是重载...程序只能有第一种形式或第二种形式之一,不能同时存在。
return
语句。 - dreamlaxmain()
函数并获取其地址;而C++则会施加C所没有的限制。 - Jonathan Lefflermain
函数唯一有趣的事情是,据我理解,标准措辞使得在递归调用时不能将负值传递给 argc
(5.1.2.2.1未指定对 argc
和 argv
的限制仅适用于对main
的初始调用)。 - 6502main
只是链接器决定的起始地址的名称,其中main
是默认名称。程序中的所有函数名都是函数开始的起始地址。
函数参数被推入/弹出堆栈,因此如果没有为函数指定参数,则不会将任何参数推入/弹出堆栈。这就是为什么main
可以在有或没有参数的情况下工作的原因。
main
(如果您可以将术语“声明”应用于main
)。实际上,您可以编写如下内容:int main(int only_one_argument) {
// code
}
它仍将编译并运行。
main
非常宽容,因为还有一个问题没有提到:即使是main
的更多参数!“Unix(但不是Posix.1)和Microsoft Windows”添加了char ** envp
(我记得DOS也允许这样做,不是吗?),而Mac OS X和Darwin又添加了另一个“任意操作系统提供的信息”char *指针。[维基百科](http://en.wikipedia.org/wiki/Main_function) - Jongware int main(int argc, char * argv[])
{
//code
}
变量argc存储传递的数据计数,而argv是一个指向从控制台传递的值的char指针数组。否则最好使用
int main()
{
//Code
}
无论如何,一个程序中只能有一个main()函数。因为这是程序开始执行的唯一起点,所以不能有多个。
您不需要覆盖此函数,因为一次只会使用一个。是的,有两个不同版本的主函数。
C
(或者实际上几乎任何有这种构造的语言中)一个程序只能有一个main
方法。 - Kyle Strandmain
)的深入讨论,我推荐阅读 John R. Levine 经典著作《Linkers & Loaders》。请注意,本书需要英语阅读能力。 - Andreas Spindlerint main(void);
是可以的,int main() { }
也是可以的。但是int main(void) { /* some code*/ }
中的void
是多余的。 - harper()
形式已经过时了,而且即使是对于main
函数也不清楚它是否被允许(除非实现明确将其作为允许的形式进行记录)。 C标准(请参见5.1.2.2.1程序启动)没有提到()
形式,这与()
形式并不完全等价。细节太长了,无法在此评论中列出。 - Keith Thompson