_Generic
在C11中可用,在此之前在C99中,tgmath.h
包含使用编译器特定hack的类似功能。
但是 Main 如何在 K&R C 或 C89/C90 中有多个签名呢?
我知道 main() 至少有 2 个函数签名:
1:int main(int argc, const char *argv[]);
2:int main(void);
但是main如何在K&R C或C89/C90中有多个签名?
main
在K&R C中本身没有多个签名。 那个版本没有你的意思是"签名"的意思。 尽管函数确实对其参数的数量和类型有期望,并且只有在满足这些期望时才定义它们的行为,但函数参数并不构成函数声明的一部分。
以下引文来自第一版《C编程语言》(Kernighan & Ritchie, 1978)第5.11节,可能很有启发性:
当调用
main
开始执行时,使用两个参数调用它。
该语句是无条件的:main
(总是)用 C 中的两个参数调用,如 K&R 所述。 编译器可以做任何他们想要或需要的事情来处理未声明这些参数的情况。
在C90或任何更高版本的C中,情况并没有真正的不同(所有这些版本仍然支持K&R风格的函数定义)。 即使使用原型声明main
,实现也会执行它们想要或需要执行的任何操作。 例如,也许它们为标准签名生成代码,并在链接期间对main()
执行任何必要的递归调用修补。 或者,他们可能为提供的任何(支持的)main()
声明生成代码,并在某种特定于操作系统的包装器中处理它。 也许在某些实现中甚至不需要什么特别的东西。
C 标准只要求实现支持问题中给出的两个签名,
1: int main(int argc, const char *argv[]);
2: int main(void);
对于调用方将参数从调用堆栈中弹出的调用约定,(1) 的调用序列适用于 (2) - 调用方将参数推送到堆栈上,被调用方 (main
) 从不使用它们,调用方从堆栈中删除它们。
对于被调用方从调用堆栈中弹出参数的调用约定,必须根据使用的签名以不同的方式编译main
。在 C 运行时中使用固定启动代码段的实现中,这将是一个问题,因为它不知道如何声明main
。处理这个问题的最简单方法是始终对main
使用"caller pops"调用约定,这实际上是Microsoft的C编译器的工作方式 - 例如,参见 https://learn.microsoft.com/en-us/cpp/build/reference/gd-gr-gv-gz-calling-convention,它指出其他调用约定在应用于main
时被忽略。
附言
_Generic和tgmath.h对此没有任何影响。
K&R C中没有签名,只有参数的名称和它们的可选类型声明,因此
main
只有一个可能的调用约定。
因此,几十年来,这些语言变化都没有对main
的称呼方式产生任何影响。
>C
有并且没有破坏的函数签名。 当然没有什么特定于参数的。 大多数编译器在前面(有些附加)下划线("_")来创建穷人的链接器命名空间,这使得防止符号名称冲突变得容易。
因此,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