我知道有两种不同的签名可以编写主方法 -
int main()
{
//Code
}
或者为了处理命令行参数,我们将其写为 -
int main(int argc, char * argv[])
{
//code
}
在C++
我知道我们可以重载一个方法,但在C
中,编译器如何处理这两个main
函数的不同签名?
答案 0 :(得分:129)
C语言的一些功能最初只是偶然发生的黑客攻击。
主要和可变长度参数列表的多个签名是这些功能之一。
程序员注意到他们可以将额外的参数传递给函数,并且在给定的编译器中没有任何不好的事情发生。
如果调用约定是这样的话,就是这种情况:
遵循这些规则的一组调用约定是基于堆栈的参数传递,其中调用者弹出参数,并从右向左推送:
;; 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
可以是不带参数的函数,在这种情况下,它不会被推到堆栈上的项目。如果它是两个参数的函数,那么它会找到argc
和argv
作为两个最顶层的堆栈项。如果它是一个特定于平台的三参数变体,带有一个环境指针(一个公共扩展名),它也可以工作:它会发现第三个参数是堆栈顶部的第三个元素。
因此固定呼叫适用于所有情况,允许将单个固定启动模块链接到程序。该模块可以用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
语句的情况下终止的行为,那么行为就好像{{1}被执行了。这也可以通过代码转换来处理。编译器注意到正在编译名为return 0
的函数。然后它检查身体的末端是否可能到达。如果是,则插入main
答案 1 :(得分:34)
即使在C ++中也没有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
的类型可以写为{{1}和,和 等等。
对于标准C ++:
3.6.1主要功能[basic.start.main]
1程序应包含一个名为main的全局函数,它是程序的指定开始。 [...]
2实现 不得 预定义主函数。 此功能不得超载。它应该 返回类型为int类型,但其类型是实现定义。 所有实施 应允许以下两个主要定义:
char **argv
和
int main() { /* ... */ }
C ++标准明确地说“它[主函数]应该具有int类型的返回类型,但是其类型是实现定义的”,并且需要与C标准相同的两个签名。
在托管环境(也支持C库的C环境)中 - 操作系统调用int main(int argc, char* argv[]) { /* ... */ }
。
在非托管环境(一个用于嵌入式应用程序)中,您始终可以使用预处理器指令(如
)更改程序的入口点(或出口)main
其中priority是可选的整数。
Pragma启动在main(优先级)之前执行该函数,而pragma exit在main函数之后执行该函数。如果有多个启动指令,则优先级决定哪个将首先执行。
答案 2 :(得分:8)
无需重载。是的,有2个版本,但当时只能使用一个版本。
答案 3 :(得分:5)
这是C和C ++语言的一个奇怪的不对称和特殊规则。
在我看来,它只是出于历史原因而存在,并且背后没有真正严重的逻辑。请注意,main
也是特殊的,原因还有其他原因(例如,C ++中的main
不能递归,你不能使用它的地址,在C99 / C ++中你可以省略最后的return
语句)。
另请注意,即使在C ++中,它也不是重载...程序具有第一种形式或者具有第二种形式;它不能兼得。
答案 4 :(得分:4)
main
的不同之处并不在于它可以通过多种方式定义,而是只能以两种不同的方式定义
main
是用户定义的函数;实现没有声明它的原型。
foo
或bar
也是如此,但您可以按照自己喜欢的方式定义具有这些名称的函数。
不同之处在于main
是由实现(运行时环境)调用的,而不仅仅是您自己的代码。实现不仅限于普通的C函数调用语义,因此它可以(并且必须)处理一些变化 - 但它不需要处理无限多种可能性。 int main(int argc, char *argv[])
形式允许使用命令行参数,C中的int main(void)
或C ++中的int main()
只是对不需要处理命令行参数的简单程序的便利。 / p>
至于编译器如何处理它,它取决于实现。大多数系统可能具有使两种形式有效兼容的调用约定,并且传递给没有参数定义的main
的任何参数都被静静地忽略。如果没有,编译器或链接器就不会特别处理main
。如果您对系统的工作原理感到好奇,那么您可能会看一些汇编列表。
与C和C ++中的许多内容一样,细节主要是由语言设计者及其前辈的历史和任意决策造成的。
请注意,C和C ++都允许main
的其他实现定义的定义 - 但很少有充分的理由使用它们。对于独立实现(例如没有OS的嵌入式系统),程序入口点是实现定义的,甚至不一定称为main
。
答案 5 :(得分:3)
main
只是链接器决定的起始地址的名称,其中main
是默认名称。程序中的所有函数名称都是函数启动的起始地址。
函数参数在堆栈上/从堆栈中弹出/弹出,因此如果没有为函数指定参数,则堆栈上/下不会推送/弹出参数。这就是main可以在有或没有参数的情况下工作。
答案 6 :(得分:2)
嗯,同一个函数main()的两个不同签名只有在您需要它们时才会显示在图片中,我的意思是如果您的程序在任何实际处理代码之前需要数据,您可以通过使用 -
int main(int argc, char * argv[])
{
//code
}
其中变量argc存储传递的数据计数,argv是指向char的指针数组,指向从控制台传递的值。 否则,与
一起使用总是好的 int main()
{
//Code
}
然而,在任何情况下,程序中都只能有一个main(),因为这是程序开始执行的唯一点,因此它不能超过一个。 (希望它值得)
答案 7 :(得分:2)
之前曾问过类似的问题:Why does a function with no parameters (compared to the actual function definition) compile?
排名靠前的答案之一是:
在C
func()
中表示您可以传递任意数量的参数。如果你 不需要参数,那么你必须声明为func(void)
所以,我想这是main
的声明方式(如果你可以将术语“声明”应用于main
)。事实上你可以这样写:
int main(int only_one_argument) {
// code
}
它仍然可以编译并运行。
答案 8 :(得分:0)
你不需要覆盖它。因为一次只能使用一个。主要功能有2个不同版本