为什么C main函数可以使用或不使用参数进行编码?

只是想知道为什么这样

int main(void){} 

编译和链接

这样做:

 int main(int argc, char **argv){} 

为什么不需要一个或另一个?

gcc甚至会编译并链接一个参数:

 int main(int argc){} 

但是用-Wall发出这个警告:

 smallest_1.5.c:3:1: warning: 'main' takes only zero or two arguments [-Wmain] 

我不是在问“他们怎么允许这个?” 但正如“调用者和链接器如何处理主要的多种可能性?”

我在下面采用Linux的观点。

mainfunction在标准定义中非常特殊(对于托管的C11实现)。 最近的编译器( GCC和Clang / LLVM ……)也明确地知道它具有处理main特定代码(并且给你这个警告)。 BTW,GCC(在GNU libc头文件的帮助下通过函数属性 )也有printf特殊代码。 您可以使用MELT为GCC添加自己的自定义function,以获得自己的function属性。

对于链接器 , main通常是一个通常的符号,但它是从crt0调用的 (使用gcc -v编译代码以了解它的真正含义)。 顺便说一下, ld(1)链接器(和ELF文件,例如可执行文件或目标文件 )没有类型或函数签名的概念,只处理名称(这就是C ++编译器做一些名称修改的原因 )。

并且ABI和调用约定是如此定义的,以便将未使用的参数传递给函数(如main或甚至open(2) …)不会造成任何伤害(在寄存器中传递多个参数)。 有关详细信息,请阅读x86-64 System V ABI 。

另请参阅本答案中的参考资料。

最后,你真的应该将你的main定义为int main(int argc, char**argv)而不是其他任何东西,你希望它们应该通过它们来处理程序参数(至少是由GNU编码强制的--help &– --version 标准 )。 在Linux上,我讨厌程序(我诅咒他们的程序员)不这样做(所以处理--help &– --version )。

因为调用代码可以例如在寄存器或堆栈中传递参数。 两个参数main使用它们,而零参数main对它们没有任何作用。 就这么简单。 链接甚至没有进入图片。

如果你担心被调用代码中的堆栈调整, main函数只需要确保堆栈指针在返回时是相同的(通常这甚至不重要,例如当ABI声明调用者负责堆栈管理)。

程序启动时调用的函数名为main。 该实现声明此函数没有原型。 它应该使用返回类型int并且没有参数来定义:

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

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

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

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

关于参数:

一个计算提供给程序的参数第二个是指向字符串的指针数组,这些字符串是那些参数 。 这些参数由命令行解释器传递给程序。 因此,这两种可能性被处理为:

  1. 如果没有声明参数:不希望参数作为输入。

  2. 如果main()有参数,它们应该:

    • argc大于零。
    • argv[argc]是一个空指针。
    • argv[0]argv[argc-1]是指向字符串的指针,其含义将由程序确定。
    • argv[0]将是包含程序名称的字符串,如果不可用,则为空字符串。 argv剩余元素表示提供给程序的参数。 如果只支持单个大小写字符,这些字符串的内容将以小写forms提供给程序。

在记忆中:

它们将被放置在返回地址和保存的基指针上方的堆栈上(就像任何其他堆栈帧一样)。

在机器级别:

它们将通过寄存器传递,具体取决于实现。

简短的回答:如果你不使用参数,那么你可以用两种方式声明不带参数的main:

 int main(void) 

要么

 int main() 

第一个手段main是一个没有参数的函数。 第二个装置main是具有任意数量参数的函数。

由于您不访问参数,两者都可以。 任何具有“特殊”代码来检查main参数的编译器都是错误的。 (但是: main 必须返回一个值。)

使其工作与可执行文件的二进制格式和OS的加载器有关 。 链接器不关心(它关心一点:它需要标记入口点)并且唯一的调用者例程是加载器。

任何系统的加载器必须知道如何将支持的二进制格式带入内存并分支到入口点。 这根据系统和二进制格式略有不同。


如果您对特定的OS /二进制格式有疑问,可能需要澄清一下。