我被问到一个面试问题,将C或C ++程序的入口点从main()
更改为任何其他函数。这怎么可能?
在标准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
。
然而,所有这些虽然可能具有学术兴趣,但并没有改变这样一个事实,即在我几十年的切割代码中,我无法想到一个单独的情况,这将是必要或可取的。
我会问面试官:你为什么要这样做?
我认为在链接之前很容易从对象中删除不需要的main()符号。
不幸的是,g ++的入口点选项对我不起作用(二进制文件在进入入口点之前崩溃)。所以我从目标文件中删除了不需要的入口点。
假设我们有两个包含入口点函数的源。
编译完成后(g ++ -c选项)我们可以得到以下目标文件。
所以我们可以使用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
是的,我们可以将主函数名称更改为任何其他名称,例如。开始,bob,rem等
编译器如何知道必须在整个代码中搜索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()将调用它。
免责声明:我使用空括号来表示一个方法,而不关心有多少参数。
入口点实际上是_start
函数(在crt1.o中实现)。
_start
函数准备命令行参数,然后调用main(int argc,char* argv[], char* env[])
,您可以通过设置链接器参数将入口点从_start
更改为mystart
:
g++ file.o -Wl,-emystart -o runme
当然,这是入口点_start
的替代品,因此您将无法获得命令行参数:
void mystart(){
}
请注意,具有构造函数或析构函数的全局/静态变量必须在应用程序开始时初始化并在结束时销毁。如果您计划绕过自动执行此操作的默认入口点,请记住这一点。
从C ++标准文档3.6.1主函数,
程序应包含一个名为main的全局函数,它是程序的指定开始。实现定义是否需要独立环境中的程序来定义主函数。
所以,它确实取决于你的编译器/链接器......
如果你在VS2010,this可以给你一些想法
由于它很容易理解,这不是C ++标准的强制要求,而是属于“实现特定行为”的范畴。
这是高度推测的,但您可能有一个静态初始化器而不是main:
#include <iostream>
int mymain()
{
std::cout << "mymain";
exit(0);
}
static int sRetVal = mymain();
int main()
{
std::cout << "never get here";
}
您甚至可以通过将这些东西放在构造函数中来使其“类似于Java”:
#include <iostream>
class MyApplication
{
public:
MyApplication()
{
std::cout << "mymain";
exit(0);
}
};
static MyApplication sMyApplication;
int main()
{
std::cout << "never get here";
}
现在。面试官可能已经考虑过这些,但我个人从不使用它们。原因是:
也就是说,我已经看到它被用于生产而不是init()
用于库初始化器。需要注意的是,在Windows上(根据经验),DLL中的静态可能会或可能不会根据使用情况进行初始化。
修改实际调用main()
函数的crt对象,或者提供自己的(不要忘记禁用正常链接)。
使用gcc,使用attribute((constructor))声明函数,gcc将在包括main之类的任何其他代码之前执行此函数。
对于基于Solaris的系统,我找到了this。您可以在我猜的每个平台上使用.init
部分:
pragma init (function [, function]...)
资源:
该pragma导致在初始化期间(在main之前)或在共享模块加载期间通过向.init部分添加调用来调用每个列出的函数。
这很简单:
正如您在c中使用常量时所知,编译器会执行一种“宏”,更改相应值的常量名称。
只需在代码开头包含一个#define
参数,其名称为启动函数,后跟名称main
:
例:
#define my_start-up_function (main)