在c ++中,main函数是编程如何将其更改为其他函数的入口点?

我被问到一个面试问题,可以将main()中的C或C ++程序的入口点更改为任何其他函数。 这怎么可能?

在标准C(我相信,C ++也是如此)中,你不能,至少不能用于托管环境(但见下文)。 该标准指定C代码的起点是main 。 标准(c99)并没有留下太多的论据空间:

5.1.2.2.1程序启动:(1)程序启动时调用的函数名为main。

而已。 然后,它会对参数和返回值进行一些分析,但实际上没有更改名称的余地。

这是针对托管环境的。 该标准还允许独立环境(即,没有OS,用于嵌入式系统之类的东西)。 对于独立环境:

在独立环境中(可以在没有操作系统任何好处的情况下执行C程序),程序启动时调用的函数的名称和类型是实现定义的。 除了第4节要求的最小集合之外,任何独立程序可用的库设施都是实现定义的。

你可以在C 实现中使用“trickery”,这样你就可以使它看起来像main不是入口点。 事实上,这是早期的Windows编译器将WinMain标记为起点的原因。


第一种方式:链接器可能在start.o等文件中包含一些主要的启动代码,这段代码运行以设置C环境然后调用main 。 没有什么可以阻止你用一些叫做bob东西取代它。


第二种方式:一些链接器为命令行开关提供了非常多的选项,这样您就可以在不重新编译启动代码的情况下进行更改。


第三种方式:你可以链接这段代码:

 int main (int c, char *v[]) { return bob (c, v); } 

然后你代码的入口点似乎是bob而不是main


然而,所有这些虽然可能具有学术兴趣,但并没有改变这样一个事实,即在我几十年的切割代码中,我无法想到一个单独的情况,这将是必要或可取的。

我会问面试官:你为什么要这样做?

从C ++标准文档3.6.1主函数

程序应包含一个名为main的全局函数,它是程序的指定开始。 实现定义是否需要独立环境中的程序来定义主函数。

所以,它取决于你的编译器/链接器……

入口点实际上是_start函数(在crt1.o中实现)。

_start函数准备命令行参数然后调用main(int,char*[]) ,您可以通过设置链接器参数将入口点从_start更改为mystart

 g++ file.o -Wl,-emystart -o runme 

当然,这是入口点_start的替代,因此您将无法获得命令行参数:

 void mystart(){ } 

如果你在VS2010, 这可以给你一些想法

由于它很容易理解,这不是C ++标准的强制要求,而是属于“实现特定行为”的范畴。

修改实际调用main()函数的crt对象,或者提供自己的(不要忘记禁用正常链接)。

使用gcc,使用attribute((构造函数))声明函数,gcc将在包括main之类的任何其他代码之前执行此函数。

这是高度推测的,但您可能有一个静态初始化器而不是main:

包括

 int mymain() { std::cout << "mymain"; exit(0); } static int sRetVal = mymain(); int main() { std::cout << "never get here"; } 

您甚至可以通过将这些内容放在构造函数中来使其“类似于Java”:

 #include  class MyApplication { public: MyApplication() { std::cout << "mymain"; exit(0); } }; static MyApplication sMyApplication; int main() { std::cout << "never get here"; } 

现在。 面试官可能已经考虑过这些,但我个人从不使用它们。 原因是:

  • 这是非传统的。 人们不会理解它,找到切入点是非常重要的。
  • 静态初始化顺序是不确定的。 放入另一个静态变量,如果它被初始化你就永远不会。

也就是说,我已经看到它用于生产而不是init()用于库初始化器。 需要注意的是,在Windows上(根据经验),DLL中的静态可能会或可能不会根据使用情况进行初始化。

对于基于Solaris的系统,我发现了这一点 。 您可以在我猜的每个平台上使用.init部分:

  pragma init (function [, function]...) 

资源:

该pragma通过向.init部分添加调用,在初始化期间(main之前)或共享模块加载期间调用每个列出的函数。

这很简单:

正如您在c中使用常量时所知,编译器会执行一种“宏”,更改相应值的常量名称。

只需在代码开头包含一个#define参数,其名称为启动函数,后跟名称main

例:

 #define my_start-up_function (main) 

我认为在链接之前很容易从对象中删除不需要的main()符号。

不幸的是,g ++的入口点选项对我不起作用(二进制文件在进入入口点之前崩溃)。 所以我从目标文件中删除了不需要的入口点。

假设我们有两个包含入口点函数的源。

  1. target.c包含我们不想要的main()。
  2. our_code.c包含我们想要作为入口点的testmain()。

编译完成后(g ++ -c选项)我们可以得到以下目标文件。

  1. target.o,包含我们不想要的main()。
  2. 包含testmain()的our_code.o我们希望成为入口点。

所以我们可以使用objcopy去除不需要的main()函数。

objcopy –strip-symbol = main target.o

我们也可以使用objcopy将testmain()重新定义为main()。

objcopy –redefine-sym testmain = main our_code.o

然后我们可以将它们都链接成二进制文件。

g ++ target.o our_code.o -o our_binary.bin

这对我有用。 现在,当我们运行our_binary.bin ,入口点是our_code.o:main()符号,它引用了our_code.c::testmain()函数。

在Windows上有另一种(相当非正统的)方式来改变程序的入口点: TLS 。 有关更多说明,请参阅此处: http : //isc.sans.edu/diary.html?storyid = 6655

是的,我们可以将主函数名称更改为任何其他名称,例如。 开始,鲍勃,雷等

编译器如何知道它必须在整个代码中搜索main()?

编程中没有什么是自动的。 有人做了一些工作让它看起来像我们自动。

所以在启动文件中已经定义了编译器应该搜索main()。

我们可以将名称main更改为其他任何内容,例如。 Bob然后编译器将仅搜索Bob()。

更改链接器设置中的值将覆盖入口点。 即,MFC应用程序使用值’Windows(/ SUBSYSTEM:WINDOWS)’将入口点从main()更改为CWinApp :: WinMain()。

 Right clicking on solution > Properties > Linker > System > Subsystem > Windows (/SUBSYSTEM:WINDOWS) 

修改入口点非常实用:

MFC是我们利用C ++编写Windows应用程序的框架。 我知道它很古老,但我的公司保留了一个原因! 您将在MFC代码中找不到main()。 MSDN说入口点是WinMain()。 因此,您可以覆盖基础CWinApp对象的WinMain()。 或者,大多数人覆盖CWinApp :: InitInstance(),因为基本WinMain()将调用它。

免责声明:我使用空括号来表示方法,而不关心有多少参数。