避免C程序中的主(入口点)
是否可以避免C程序中的入口点(主)。 在下面的代码中,是否可以调用func()
调用而无需通过以下程序中的main()
调用? 如果是,如何做,何时需要,为什么要给出这样的规定?
int func(void) { printf("This is func \n"); return 0; } int main(void) { printf("This is main \n"); return 0; }
如果你正在使用gcc,我发现一个线程说你可以使用-e
命令行参数指定一个不同的入口点; 所以你可以使用func
作为你的入口点,这将使main
未使用。
请注意,这实际上不允许您调用另一个例程而不是main
。 相反,它允许你调用另一个例程而不是_start
,这是libc启动例程 – 它做了一些设置,然后调用main
。 因此,如果执行此操作,您将丢失一些内置于运行时库中的初始化代码,其中可能包括解析命令行参数等内容。 使用前请阅读此参数。
如果您正在使用其他编译器,则可能有也可能没有参数。
当构建嵌入式系统固件直接从ROM运行时,我经常会避免命名入口点main()
以向代码审查者强调代码的特殊性。 在这些情况下,我提供了C运行时启动模块的自定义版本,因此很容易用其他名称(如BootLoader()
替换对main()
调用。
我(或我的供应商)几乎总是必须在这些系统中自定义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
节点加载断点,运行到该断点,然后将PC更改为指向或进入foo
并从那里继续。
因为.so文件在某些方面也与真正的可执行文件类似,所以在Linux上可能有某种类似的技巧。 当然,可以使用像调试器一样的方法。
根据经验,系统提供的加载器将始终运行main。 有了足够的权威和能力,理论上你可以编写一个不同的加载器来做其他事情。
将main重命名为func和func为main并从name调用func。
如果您可以访问源代码,则可以执行此操作并且很容易。
如果您使用的是开源编译器(如GCC)或针对嵌入式系统的编译器,则可以修改C运行时启动(CRT)以从您需要的任何入口点开始。 在GCC中,此代码位于crt0.s. 通常,此代码部分或全部在汇编程序中,对于大多数嵌入式系统,编译器示例或默认启动代码将被提供。
然而,更简单的方法是简单地将main()隐藏在链接到代码的静态库中。 如果main()的实现如下:
int main(void) { func() ; }
然后它会查看所有意图和目的,就像用户入口点是func()一样。 这是具有除main()之外的入口点的应用程序框架的工作量。 请注意,因为它位于静态库中,所以main()的任何用户定义都将覆盖该静态库版本。
解决方案取决于您使用的编译器和链接器。 总是不是 main
是应用程序的真正切入点。 真正的入口点进行一些初始化并调用例如main
。 如果使用Visual Studio编写Windows程序,可以使用链接器的/ ENTRY开关覆盖默认入口点mainCRTStartup
并调用func()
而不是main()
:
#ifdef NDEBUG void mainCRTStartup() { ExitProcess (func()); } #endif
如果您编写最小的应用程序,那么这是标准做法。 在这种情况下,您将受到C-Runtimefunction使用的限制。 您应该使用Windows API函数而不是C-Runtime函数。 例如,您应该使用OutputString(TEXT("This is func \n"))
而不是printf("This is func \n")
OutputString(TEXT("This is func \n"))
,其中OutputString
仅针对WriteFile
或WriteConsole
:
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)); }
这实际上取决于您如何调用二进制文件,并且将具有合理的平台和环境特定性。 最明显的答案是简单地将“主要”符号重命名为其他内容并将“func”称为“main”,但我怀疑这不是您要做的事情。