是否可以避免C程序中的入口点(主要)。在下面的代码中,是否可以在不通过以下程序中的func()
调用的情况下调用main()
调用?如果是,如何做,何时需要,为什么要给出这样的规定?
int func(void)
{
printf("This is func \n");
return 0;
}
int main(void)
{
printf("This is main \n");
return 0;
}
答案 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
仅在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));
}
:
{{1}}
答案 6 :(得分:0)
这实际上取决于您如何调用二进制文件,并且将具有合理的平台和环境特定性。最明显的答案是简单地将“主要”符号重命名为其他内容,并将“func”称为“main”,但我怀疑这不是您要做的事情。