C';在“通用”之前,支持的主签名有哪些不同?

C';在“通用”之前,支持的主签名有哪些不同?,c,generics,main,signature,C,Generics,Main,Signature,\u Generic在C11中可用,在C99中使用之前,tgmath.h使用特定于编译器的技巧包含类似的功能 但main在K&R C或C89/C90中是如何拥有多个签名的呢 我知道main()至少有两个函数签名: 1:intmain(intargc,constchar*argv[]) 2:int main(无效) 但main在K&R C或C89/C90中是如何拥有多个签名的呢 main在K&R C中本身没有多个签名。该版本没有您所指的“签名”的含义。虽然函数确实对其参数的数量和类型有期望,并且只

\u Generic
在C11中可用,在C99中使用之前,
tgmath.h
使用特定于编译器的技巧包含类似的功能

但main在K&R C或C89/C90中是如何拥有多个签名的呢

我知道main()至少有两个函数签名:

1:
intmain(intargc,constchar*argv[])

2:
int main(无效)

但main在K&R C或C89/C90中是如何拥有多个签名的呢

main
在K&R C中本身没有多个签名。该版本没有您所指的“签名”的含义。虽然函数确实对其参数的数量和类型有期望,并且只有在满足这些期望时才定义它们的行为,但函数参数并不构成函数声明的一部分

以下引用自《C编程语言》(Kernighan&Ritchie,1978)第一版第5.11节的内容可能会有所启发:

当调用
main
开始执行时,将使用两个参数调用它

该语句是无条件的:
main
是(始终)用K&R所描述的C语言中的两个参数调用的。编译器可以做任何他们想要或需要的事情来处理那些未声明参数的情况


在C90或任何更高版本的C中(所有这些版本仍然支持K&R风格的函数定义),情况并没有什么不同。即使使用原型声明了
main
,实现也会做任何他们想做或需要做的事情。例如,它们可能为标准签名生成代码,并在链接过程中对
main()
执行任何必要的递归调用修补。或者他们可能会为
main()
提供的任何(受支持的)声明生成代码,并在某种特定于操作系统的包装器中处理它。也许在某些实现中甚至不需要什么特殊的东西。

C
有并且没有被屏蔽的函数签名。当然没有具体的参数。大多数编译器都预先添加了一个下划线(“\”)来创建穷人的链接器名称空间,这使得防止符号名冲突变得很容易

因此,C运行时启动总是有一个明确的符号来启动。最常见的
\u main

start:
 ;#  set up registers
 ;#  set up runtime environment:
 ;#  set up stack, initialize heap, connect stdin, stdout, stderr, etc.
 ;#  obtain environment and format for use with "envp"
 ;#  obtain command line arguments and set up for access with "argv"
 push  envp
 push  argv
 push  argc    ; number of arguments in argv
 call  _main

 push  r0
 call  exit

.end  start

C标准仅要求实施支持问题中给出的两个签名

1: int main(int argc, const char *argv[]);

2: int main(void);
对于调用方将参数从调用堆栈中弹出的调用约定,(1)的调用序列在(2)中运行良好——调用方将参数推送到堆栈上,被调用方(
main
)从不使用它们,调用方将它们从堆栈中移除

对于被调用方从调用堆栈弹出参数的调用约定,
main
必须根据使用的签名进行不同的编译。在C运行时中使用固定启动代码段的实现中,这将是一个问题,因为它不知道
main
是如何声明的。处理这一问题的最简单方法是始终对
main
使用“调用者弹出式”调用约定,这实际上就是Microsoft的C编译器的工作方式——例如,请参见,其中说明在应用于
main
时忽略其他调用约定

start:
 ;#  set up registers
 ;#  set up runtime environment:
 ;#  set up stack, initialize heap, connect stdin, stdout, stderr, etc.
 ;#  obtain environment and format for use with "envp"
 ;#  obtain command line arguments and set up for access with "argv"
 push  envp
 push  argv
 push  argc    ; number of arguments in argv
 call  _main

 push  r0
 call  exit

.end  start
附言

  • _Generic和tgmath.h对这些都没有影响

  • K&R C中没有签名,只有参数的名称和可选类型声明,因此
    main
    只有一种可能的调用约定

  • start:
     ;#  set up registers
     ;#  set up runtime environment:
     ;#  set up stack, initialize heap, connect stdin, stdout, stderr, etc.
     ;#  obtain environment and format for use with "envp"
     ;#  obtain command line arguments and set up for access with "argv"
     push  envp
     push  argv
     push  argc    ; number of arguments in argv
     call  _main
    
     push  r0
     call  exit
    
    .end  start
    

因此,几十年来这些语言变化都没有对
main
的调用方式产生任何影响。

除了“特定于编译器的黑客”之外,你在寻找什么答案呢?
main()
的多个签名与
\u Generic
没有任何关系,调用
main
的代码是特定于编译器的,因此可以包含任意数量的必要攻击。此外,宣言只是一个宣言。实际定义不必与声明一致,只要它或多或少与调用约定相匹配。具体来说:调用方可以提供argc和argv,被调用方可以忽略它们,因为C调用约定允许这样做。
main()
可以有几个不同的签名之一,但不能有多个签名。只允许一个。
\u start
(在
crt0.o
中定义)调用
\u libc\u start\u main
,它最终总是调用
int main(int argc,const char*argv[],const char*envp)。对于不同的架构,C将处理
intmain(void)
以不同的方式,但在x86和大多数其他体系结构上,忽略尾随参数是有效的,因为支持stackframes的体系结构会自动进行清理。需要做一些小的澄清:“C89/C90标准只允许问题中给出的两个签名。”C99添加了实现定义的内容。@user694733谢谢,调整了措辞。