我知道写主要方法有两种不同的签名-

int main()
{
   //Code
}

或者为了处理命令行参数,我们将其写成-

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

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

推荐答案

C语言的一些特性最初只是碰巧起作用的黑客攻击.

main的多个签名以及可变长度的参数列表就是这些特性之一.

程序员注意到,他们可以向函数传递额外的参数,并且给定的编译器不会发生任何不好的事情.

如果调用约定满足以下条件,则会出现这种情况:

  1. 调用函数清除参数.
  2. 最左边的参数更靠近堆栈顶部或堆栈框架的底部,因此伪参数不会使寻址无效.

遵循这些规则的一组调用约定是基于堆栈的参数传递,调用者通过该协议弹出参数,并将其从右向左推:

 ;; 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可以是一个无参数的函数,在这种情况下,它对推到堆栈上的项不起作用.如果它是两个参数的函数,那么它会发现argcargv是最上面的两个堆栈项.如果它是一个平台特定的三参数变量,带有一个环境指针(一个公共扩展名),那么它也会工作:它会发现第三个参数是堆栈顶部的第三个元素.

因此,固定调用适用于所有情况,允许单个固定启动模块链接到程序.该模块可以用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));
}

换句话说,这个start模块总是调用一个三参数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函数(或者不管它叫什么),或者至少从几个预编译的备选方案中 Select 一个.可以将关于正在使用main的哪种支持形式的信息存储在对象文件中.链接器可以查看此信息,并 Select 包含与程序定义兼容的main调用的启动模块的正确版本.C实现通常只有少量支持的main形式,所以这种方法是可行的.

C99语言的编译器总是需要在一定程度上特别处理main,以支持这样一种攻击:如果函数在没有return语句的情况下终止,行为就好像执行了return 0.这同样可以通过代码转换来处理.编译器注意到正在编译一个名为main的函数.然后判断身体的末端是否有可能到达.如果是,则插入一个return 0;

C++相关问答推荐

InetPton()函数无效的IP地址

GCC:try 使用—WError或—pedantic using pragmas

找出文件是否包含给定的文件签名

Tiva TM4C123GXL的I2C通信

当打印字符串时,为什么在c中没有使用常量限定符时我会收到警告?

如何使用低级C++写出数值

在C中将通用字符名称转换为UTF-8

C由四个8位整数组成无符号32位整数

struct 上的OpenMP缩减

我的程序在收到SIGUSR1信号以从PAUSE()继续程序时总是崩溃()

对于C中给定数组中的每个查询,如何正确编码以输出给定索引范围(1到N)中所有数字的总和?

如何使用C++在控制台中以彩色打印被阻止的客户端

C将数组传递给函数以修改数组

C中的回文数字

*S=0;正在优化中.可能是GCC 13号虫?或者是一些不明确的行为?

Linux/C:带有子进程的进程在添加waitid后都挂起

分配给静态变量和动态变量的位置之间有区别吗?

指向返回 struct 成员的指针,安全吗?

无法在线程内用 C 打印?

如何让 unlinkat(dir_fd, ".", AT_REMOVEDIR) 工作?