35

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

4

13 回答 13

51

在标准 C 中(我相信 C++ 也是如此),你不能,至少不能在托管环境中(但见下文)。该标准规定 C 代码的起点是main. 标准(c99)没有留下太多争论的余地:

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

而已。然后它对参数和返回值进行了一些讨论,但实际上没有任何改变名称的余地。

这是针对托管环境的。该标准还允许一个独立的环境(即,没有操作系统,例如嵌入式系统)。对于独立环境:

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

你可以在 C实现中使用“诡计”,这样你就可以让它看起来main不是入口点。这实际上是早期的 Windows 编译器将其标记WinMain为起点的做法。


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


第二种方式:一些链接器通过命令行开关提供该选项,以便您可以更改它而无需重新编译启动代码。


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

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

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


然而,所有这一切,虽然可能具有学术兴趣,但并没有改变这样一个事实,即在我数十年的代码剪切过程中,我想不出一个单独的情况,这将是必要的或可取的。

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

于 2010-10-20T04:51:27.370 回答
12

来自 C++ 标准文档3.6.1 Main Function

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

所以,它确实取决于你的编译器/链接器......

于 2010-10-20T04:59:55.607 回答
12

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

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

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

当然,这是入口点的替代品,_start因此您不会获得命令行参数:

void mystart(){

}

请注意,具有构造函数或析构函数的全局/静态变量必须在应用程序开始时初始化并在结束时销毁。如果您打算绕过自动执行的默认入口点,请记住这一点。

于 2012-08-30T11:27:51.697 回答
7

如果您使用的是 VS2010,可能会给您一些想法

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

于 2010-10-20T04:48:15.320 回答
4

这是高度推测性的,但您可能有一个静态初始化程序而不是 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 中的静态变量可能会也可能不会根据使用情况进行初始化。

于 2016-06-29T07:05:27.230 回答
3

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

于 2010-10-20T04:53:08.183 回答
3

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

于 2015-10-05T16:21:36.347 回答
2

这很简单:

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

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

例子:

#define my_start-up_function (main)
于 2013-11-27T15:17:31.127 回答
2

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

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

资源:

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

于 2010-10-20T18:09:24.427 回答
2

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

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

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

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

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

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

所以我们可以使用 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()函数的符号。

于 2016-11-30T21:28:03.923 回答
1

在 Windows 上,还有另一种(相当非正统的)方法来更改程序的入口点:TLS. 有关更多解释,请参见:http: //isc.sans.edu/diary.html ?storyid=6655

于 2010-10-20T09:56:29.123 回答
0

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

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

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

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

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

于 2013-05-31T04:16:35.323 回答
0

更改链接器设置中的值将覆盖入口点。即,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() 会调用它。

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

于 2018-10-26T15:40:54.773 回答