【问题标题】:Why can C main function be coded with or without parameters?为什么 C 的 main 函数可以带或不带参数编码?
【发布时间】:2015-09-13 13:21:54
【问题描述】:

只是想知道为什么

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]

我不是在问“他们为什么允许这样做?”但就像“调用者和链接器如何处理 main 的多种可能性?”

【问题讨论】:

  • 简短的回答是“因为标准是这样说的”。
  • @JefferyThomas 我的问题不是“他们为什么允许它”而是“它是如何工作的”?调用例程和链接器如何处理任何一种定义?
  • 因为调用代码可以,例如,在寄存器或堆栈中传递参数。两个参数 main 使用它们,而零参数 main 不使用它们。就是这么简单。链接甚至没有出现。
  • 非常相关question

标签: c


【解决方案1】:

我在下面采取 Linux 的观点。

main 函数在标准定义中非常特殊(用于托管的 C11 实现)。最近的编译器(GCCClang/LLVM....)也明确知道它有特定的代码来处理main(并给你这个警告)。顺便说一句,GCC(在GNU libc 标题到function attributes 的帮助下)也有printf 的特殊代码。您可以使用 MELT 为您自己的函数属性添加您自己的自定义到 GCC。

对于linkermain 通常是一个常用符号,但它是从crt0 调用的(使用gcc -v 编译代码以了解其真正含义)。顺便说一句,ld(1) 链接器(和ELF 文件,例如executablesobject files)没有类型或函数签名的概念,只处理名称(这就是为什么C++ 编译器会做一些name mangling)。

ABIcalling conventions 的定义如此定义,以至于将未使用的参数传递给函数(如 main 甚至 open(2)...)不会造成任何伤害(在寄存器中传递了几个参数) .阅读x86-64 System V ABI了解详情。

另请参阅this answer 中的参考资料。

最后,您实际上应该将您的main 定义为int main(int argc, char**argv) 而不是其他任何东西,并且希望您应该通过它们通过handle 程序参数(至少--help--version 按照GNU coding standards 的要求)。在 Linux 上,我讨厌 程序(并且我诅咒他们的程序员)不这样做(所以处理--help--version)。

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

      【解决方案3】:

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

      int main(void)
      

      int main()
      

      第一个意思是main是一个没有参数的函数。第二种表示main 是一个带有任意数量参数的函数。

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

      【讨论】:

      【解决方案4】:

      程序启动时调用的函数名为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 的其余元素表示提供给程序的参数。在仅支持单写字符的情况下,这些字符串的内容将以小写形式提供给程序。

      在内存中:

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

      在机器级别:

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

      【讨论】:

      • 但是 C 不允许重载函数。
      • @el.pescado 提醒你,我写过“One can think of the main() ...”。不过,这是个好问题!
      • 然而,在这种概括中存在缺陷。函数重载通常通过将函数签名附加到符号名称来工作(例如,通过 C++ 中的名称修饰),以便两个函数虽然具有相同的名称,但被分配不同的符号。在这种情况下,它可以工作,因为没有发生这样的事情 - 任何形式的“main”都具有相同的符号名称(大多数情况下是“main”或“_main”)。
      • 此外,尽管main 的任何形式都可以使用,但您仍然只能拥有一个。
      • @el.pescado 感谢您的洞察力!我将解释答案的介绍。
      【解决方案5】:

      使其工作与executablebinary format 和操作系统的loader 有关。链接器不关心(它有点关心:它需要标记入口点),唯一的调用例程是加载程序。

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


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

      【讨论】:

        猜你喜欢
        • 2019-09-11
        • 2015-03-02
        • 2023-03-29
        • 1970-01-01
        • 2019-07-16
        • 1970-01-01
        • 2022-11-02
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多