CRT如何调用main,具有不同的参数

时间:2012-04-29 12:54:05

标签: c++ c

我们可以用几种方式编写主要功能,

  1. int main()
  2. int main(int argc,char *argv[])
  3. int main(int argc,char *argv[],char * environment)
  4. 运行时CRT函数如何知道应该调用哪个main。请注意这里,我不是在询问是否支持Unicode。

5 个答案:

答案 0 :(得分:5)

接受的答案是不正确的,CRT中没有特殊代码来识别main()声明的类型。

它的工作原理是因为cdecl调用约定。其中指定参数从右向左推入堆栈,并且调用者在调用后清理堆栈。所以CRT只是将所有参数传递给main(),并在main()返回时再次弹出它们。您需要做的唯一事情是在main()函数声明中以正确的顺序指定参数。 argc参数必须是第一个,它是堆栈顶部的参数。 argv必须是第二名,等等。省略一个论点没有区别,只要你省略所有后面的那些。

这也是printf()函数可以工作的原因,它有可变数量的参数。在已知位置使用一个参数,第一个参数。

答案 1 :(得分:4)

通常,编译器/链接器需要识别您正在使用的main的特定形式,然后包含代码以使其从系统启动函数适应您的C或C ++ main函数。

确实,特定平台上的特定编译器可以使用Hans在其答案中描述的方法在不执行此操作的情况下逃脱。但是,并非所有平台都使用堆栈来传递参数,并且可以编写具有不兼容参数列表的符合C和C ++的实现。对于这种情况,编译器/链接器需要确定调用哪种形式的main

答案 2 :(得分:3)

嗯。似乎当前接受的答案,表明先前接受的答案是不正确的,本身是不正确的。这个问题上的标签表明它适用于C ++和C,因此我将坚持使用C ++规范,而不是C99。无论所有其他解释或论点如何,这个问题的主要答案是“main()在实现定义的方式中被视为特殊。”我相信大卫的答案在技术上比汉斯更正确,但我会解释它更详细......

main()函数很有趣,由编译器&处理。具有与其他功能不匹配的行为的链接器。汉斯是正确的,CRT中没有特殊的代码来识别main()的不同签名,但他断言它“因cdecl调用约定而起作用”仅适用于特定的平台,特别是Visual Studio。 CRT中没有特殊代码识别main()的不同签名的真正原因是没有必要。虽然它有点分裂,但它是链接器,它的工作是在启动时将启动代码绑定到main(),这不是CRT在启动时的工作。

根据C ++规范(参见第3.6节“启动和终止”),main()函数的大部分处理方式是实现定义的。大多数实现的编译器可能会隐式地使用类似于extern“C”链接的东西来处理main(),使main()处于非装饰状态,这样无论其函数原型如何,其链接器符号都是相同的。或者,实现的链接器可以足够智能地扫描符号表,查找任何其修饰名称解析为某种形式的“[int | void] main(...)”(注意void作为返回类型是本身是一个特定于实现的东西,因为规范本身说main()的返回类型必须是'int')。一旦在可用符号中找到这样的函数,链接器就可以简单地使用启动代码引用“main()”的那个函数,因此确切的符号名称不一定必须匹配任何东西;它甚至可以是wmain()或其他,只要链接器知道要查找的变量,或者编译器赋予所有变量具有相同的符号名称。

另外需要注意的是规范说main()可能没有重载,因此链接器不应该在各种形式的main()的多个用户实现之间“选择”。如果找到多个,则即使参数列表不匹配,也会出现重复的符号错误(或其他类似的错误)。虽然所有实现都“允许”允许两者

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

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

还允许它们允许其他参数列表,包括您显示的包含环境字符串数组指针的版本,以及在任何给定实现中有意义的任何其他变体。

正如Hans所说,Visual Studio编译器的cdecl调用约定(以及许多其他编译器的调用约定)提供了一个框架,其中调用者可以设置调用环境(即堆栈,或ABI定义的寄存器,或某些组合这两种方式可以传递可变数量的参数,并且当被调用者返回时,调用者负责清理(从堆栈中弹出已使用的参数空间,或者在寄存器的情况下,不需要执行任何操作)清理)。这种设置非常适合启动代码传递比可能需要的更多参数,并且用户的main()实现可以自由使用或不使用任何这些参数,就像许多平台对各种形式的处理一样。你在问题中列出的main()。但是,这不是编译器+链接器实现此目标的唯一方法:相反,链接器可以根据main()的定义在各种版本的启动代码之间进行选择。这样做会允许各种main()参数列表,否则使用cdecl调用者清理模型是不可能的。并且因为所有这些都是实现定义的,所以只要编译器+链接器支持上面显示的两个组合(int main()int main(int, char**)),它就符合C ++规范是合法的。

答案 3 :(得分:2)

C 99标准(5.1.2.2.1程序启动)表示实现为main()函数强制执行 no 原型,并且程序可以将其定义为:

1)int main(void);

2)int main(int argc,char * argv []);

或以语义上等同于2的方式,例如

2')int main(int argc,char ** argv);

或以其他实施方式定义。 要求原型:

3)int main(int argc,char * argv [],char * envp []);

将具有预期的行为 - 尽管该原型必须编译,因为任何原型必须编译。 3)由GCC和Microsoft C以及其他编译器支持。 (N.B.提问者的 第3个原型有char * envp而不是char * envp [],无论是偶然还是因为他/她有其他编译器。)

GCC和Microsoft C都将使用任何原型编译main(),因为它们应该如此。它们解析您实际指定的原型并生成汇编语言,以正确的方式使用参数(如果有)。因此,例如,他们将为程序生成预期的行为:

#include <stdio.h>

void main(double d, char c)
{
    printf("%lf\n",d);
    putchar(c);
}

如果你能找到一种方法将double和char直接传递给程序,而不是通过字符串数组。

可以通过启用实验程序的汇编语言列表来验证这些观察结果。

编译器的标准CRT如何允许我们调用生成的main()实现的问题不同于如何为编译器定义main()的问题。

对于GCC和MS C,main()可以定义我们喜欢的任何方式。但是,在每种情况下,实现的标准CRT,AFIK都支持将参数传递给main(),而不是按照3)。因此1) - 2')也会通过忽略多余的参数而具有预期的行为,并且除了提供我们自己的非标准运行时之外,我们没有其他选择。

Hans Passant的回答似乎偶然误导了 argc 告诉函数以与printf()的第一个参数相同的方式消耗多少个后续参数。如果 argc 完全存在,则它仅表示作为第二个参数 argv 传递的数组中的元素数。它不表示向main()传递了多少个参数。 GCC和MS C都会通过解析你编写的原型来计算出预期的参数是什么 - 本质上是编译器对任何函数所做的事情,除了那些,比如printf(), 被定义为采用可变数量的参数。

main()不接受可变数量的参数。它接受您在定义中指定的参数,并且通常编译器的标准CRT假定它们是(int,char * [],char * [])。

答案 4 :(得分:1)

首先,main函数在GCC中具体处理(例如GCC 4.7源代码树的文件main_identifier_node中的gcc/c-family/c-common.c

C11和C ++ 11标准对此有具体的措辞和规范。

然后,C调用ABI约定通常是为了使额外的参数不会造成太大伤害。

所以你可以把它想象成语言规范和编译器都有关于main的“重载”的特定事情。

我甚至认为main可能不是普通的功能。我相信标准中的一些词 - 我现在没有 - 可能是例如被理解为禁止在main上取消地址或递归。

在实践中,main由一些汇编代码调用,这些汇编代码编译为crt*.o链接的gcc文件。使用gcc -v了解更多内容。