避免C程序中的主(入口点)

时间:2010-07-31 17:47:50

标签: c compiler-construction function operating-system entry-point

是否可以避免C程序中的入口点(主要)。在下面的代码中,是否可以在不通过以下程序中的func()调用的情况下调用main()调用?如果是,如何做,何时需要,为什么要给出这样的规定?

int func(void)
{
     printf("This is func \n");
     return 0;
}

int main(void)
{
     printf("This is main \n");
     return 0;
}

7 个答案:

答案 0 :(得分:23)

如果你正在使用gcc,我发现一个帖子说你可以使用-e command-line parameter指定一个不同的入口点;因此,您可以使用func作为入口点,这将使main未使用。

请注意,这实际上并不允许您调用另一个例程而不是main。相反,它允许您调用另一个例程而不是_start,这是libc启动例程 - 它进行一些设置,然后调用main。因此,如果执行此操作,您将丢失一些内置于运行时库中的初始化代码,其中可能包括解析命令行参数等内容。在使用之前请阅读此参数。

如果您正在使用其他编译器,则可能有也可能没有参数。

答案 1 :(得分:16)

当构建嵌入式系统固件直接从ROM运行时,我经常会避免命名入口点main(),以向代码审查者强调代码的特殊性。在这些情况下,我提供了C运行时启动模块的自定义版本,因此很容易将其main()的调用替换为BootLoader()等其他名称。

我(或我的供应商)几乎总是必须在这些系统中自定义C运行时启动,因为RAM要求初始化代码才能开始正常运行并不罕见。例如,典型的DRAM芯片需要其控制硬件的惊人数量的配置,并且在它们有用之前通常需要大量(数千个总线时钟周期)延迟。在完成之前,甚至可能没有放置调用堆栈的位置,因此启动代码可能无法调用任何函数。即使RAM器件在上电时工作,也几乎总有一些芯片选择硬件或一两个FPGA需要初始化才能安全地让C运行时开始初始化。

当用C编写的程序加载并启动时,某些组件负责使调用main()的环境存在。在Unix,Linux,Windows和其他交互式环境中,大部分工作是加载程序的OS组件的自然结果。但是,即使在这些环境中,也需要进行一些初始化工作才能调用main()。如果代码实际上是C ++,那么可能会有大量的工作包括为所有全局对象实例调用构造函数。

所有这些的细节由链接器及其配置和控制文件处理。链接器ld(1)有一个非常精细的控制文件,它准确地告诉它在输出中包含哪些段,在什么地址以及以什么顺序包含。找到您隐式用于工具链并读取它的链接器控制文件可能是有益的,链接器本身的参考手册和您的可执行文件必须遵循的ABI标准才能运行。

编辑:在更常见的背景下更直接地回答问题:“你能打电话给foo而不是主?”答案是“可能,但只是因为很棘手”。

在Windows上,可执行文件和DLL的文件格式几乎相同。可以编写一个程序来加载在运行时命名的任意DLL,并在其中查找任意函数并调用它。其中一个程序实际上是标准Windows发行版的一部分:rundll32.exe

由于.EXE文件可以由处理.DLL文件的相同API加载和检查,原则上如果.EXE有一个名为函数foo的EXPORTS部分,则可以编写类似的实用程序加载和调用它。当然,您不需要对main执行任何特殊操作,因为这将是自然切入点。当然,在实用程序中初始化的C运行时可能与可执行文件链接的C运行时不同。 (Google为“DLL Hell”提示。)在这种情况下,您的实用程序可能需要更智能。例如,它可以充当调试器,使用main处的断点加载EXE,运行到该断点,然后将PC更改为指向foo或进入{{1}}并从那里继续。< / p>

因为.so文件在某些​​方面与真正的可执行文件类似,所以在Linux上可能存在某种类似的技巧。当然,可以使用像调试器一样的方法。

答案 2 :(得分:5)

经验法则是系统提供的加载器总是运行main。有了足够的权威和能力,你理论上可以编写一个不同的加载器来做其他事情。

答案 3 :(得分:4)

将main重命名为func,将func重命名为main,并从名称调用func。

如果您可以访问源代码,则可以执行此操作并且很容易。

答案 4 :(得分:3)

如果您使用的是开源编译器(如GCC)或针对嵌入式系统的编译器,则可以修改C运行时启动(CRT)以从您需要的任何入口点开始。在GCC中,此代码位于crt0.s.通常,此代码部分或全部为汇编程序,对于大多数嵌入式系统,编译器示例或默认启动代码将被提供。

然而,更简单的方法是简单地将main()隐藏在链接到代码的静态库中。如果main()的实现看起来像:

int main(void)
{
    func() ;
}

然后它将查看所​​有意图和目的,就像用户入口点是func()一样。这是除了main()之外的入口点有多少个应用程序框架。请注意,因为它位于静态库中,所以main()的任何用户定义都将覆盖该静态库版本。

答案 5 :(得分:1)

解决方案取决于您使用的编译器和链接器。始终是 main是应用程序的真正入口点。真正的入口点进行一些初始化并调用例如main。如果使用Visual Studio为Windows编写程序,则可以使用链接器的/ ENTRY开关覆盖默认入口点mainCRTStartup并调用func()而不是main()

#ifdef NDEBUG
void mainCRTStartup()
{
    ExitProcess (func());
}
#endif

如果您编写的是最小的应用程序,那么这是标准做法。在这种情况下,您将受到C-Runtime功能使用的限制。您应该使用Windows API函数而不是C-Runtime函数。例如,您应使用printf("This is func \n")而不是OutputString(TEXT("This is func \n"))OutputString仅在WriteFileWriteConsole时实施static HANDLE g_hStdOutput = INVALID_HANDLE_VALUE; static BOOL g_bConsoleOutput = TRUE; BOOL InitializeStdOutput() { g_hStdOutput = GetStdHandle (STD_OUTPUT_HANDLE); if (g_hStdOutput == INVALID_HANDLE_VALUE) return FALSE; g_bConsoleOutput = (GetFileType (g_hStdOutput) & ~FILE_TYPE_REMOTE) != FILE_TYPE_DISK; #ifdef UNICODE if (!g_bConsoleOutput && GetFileSize (g_hStdOutput, NULL) == 0) { DWORD n; WriteFile (g_hStdOutput, "\xFF\xFE", 2, &n, NULL); } #endif return TRUE; } void Output (LPCTSTR pszString, UINT uStringLength) { DWORD n; if (g_bConsoleOutput) { #ifdef UNICODE WriteConsole (g_hStdOutput, pszString, uStringLength, &n, NULL); #else CHAR szOemString[MAX_PATH]; CharToOem (pszString, szOemString); WriteConsole (g_hStdOutput, szOemString, uStringLength, &n, NULL); #endif } else #ifdef UNICODE WriteFile (g_hStdOutput, pszString, uStringLength * sizeof (TCHAR), &n, NULL); #else { //PSTR pszOemString = _alloca ((uStringLength + sizeof(DWORD))); CHAR szOemString[MAX_PATH]; CharToOem (pszString, szOemString); WriteFile (g_hStdOutput, szOemString, uStringLength, &n, NULL); } #endif } void OutputString (LPCTSTR pszString) { Output (pszString, lstrlen (pszString)); }

{{1}}

答案 6 :(得分:0)

这实际上取决于您如何调用二进制文件,并且将具有合理的平台和环境特定性。最明显的答案是简单地将“主要”符号重命名为其他内容,并将“func”称为“main”,但我怀疑这不是您要做的事情。