在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 ++的入口点选项对我不起作用(二进制文件在进入入口点之前崩溃)。 所以我从目标文件中删除了不需要的入口点。
假设我们有两个包含入口点函数的源。
- target.c包含我们不想要的main()。
- our_code.c包含我们想要作为入口点的testmain()。
编译完成后(g ++ -c选项)我们可以得到以下目标文件。
- target.o,包含我们不想要的main()。
- 包含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()将调用它。
免责声明:我使用空括号来表示方法,而不关心有多少参数。