有没有GCC编译器/链接器选项可以更改main函数的名称?

15

我的软件有一个主函数,用于正常使用,还有一个不同的主函数,用于单元测试。 如果有一种方法可以通过gcc指定要使用哪个“主”函数,那就太棒了。


这个能否通过一次编译完成呢?即不需要使用“make -D TESTING; make clean; make”这些命令?我会在使用相同代码进行测试后,感到更加放心地进行发布。 - Arthur Ulfeldt
你只需要在包含主函数的文件上加上“-D”选项。我会有一个makefile来构建所有内容,包括两次编译主文件(一次带有“-D”,一次不带...注意必须使用两个不同的输出文件名进行编译)。然后将它们链接两次:一次用于测试构建,一次用于正常构建。 - mschaef
这个问题早于那个问题,并且有更具体针对此用途的答案。被接受的答案比那个问题的答案更好。 (这两个问题都很古老)我想取消它们之间的链接。 - Arthur Ulfeldt
11个回答

15
这里的其他答案都很合理,但严格来说,您遇到的问题并不是GCC的问题,而是C运行时的问题。您可以使用-e标志将入口点指定为ld来指定程序的入口点。我的文档上写着:

-e symbol_name

指定主可执行文件的入口点。默认情况下,入口名称为“start”,该名称在crt1.o中找到,其中包含设置和调用main()所需的粘合代码。

这意味着您可以覆盖入口点,但如果您想在计算机上正常运行C程序,则可能不希望这样做,因为start可能会执行各种特定于操作系统的操作,这些操作在运行程序之前是必需的。如果您能够实现自己的start,那么您就可以按照自己的意愿进行操作。

4
在Linux上:1)使用GCC时,您还需要使用-nostartfiles,否则crt1.o中的main将未定义,因此无法链接2)必须使用显式的exit()退出,否则返回到未知位置将导致段错误3)argv不会为您设置。 - Ciro Santilli OurBigBook.com

9
将它们放在不同的文件中,指定一个 .c 文件用于正常使用,另一个 .c 文件用于测试。
或者,在命令行上使用测试构建定义测试,并使用类似以下内容的东西:
int main(int argc, char *argv[])
{
#ifdef TESTING
    return TestMain(argc, argv);
#else
    return NormalMain(argc, argv);
#endif
}

int TestMain(int argc, char *argv[])
{
    // Do testing in here
}

int NormalMain(int argc, char *argv[])
{
    //Do normal stuff in here
}

7
另外,提醒原帖作者,只需将-DTESTING添加到gcc的参数列表中即可。 - Marc Bollinger
最好使用-e选项作为入口点或使用--strip-symbol。 - Alex Hoppus
@Alex: “你不能删除一个被接受的答案” :( - Billy ONeal

8
您可以使用宏将一个函数重命名为main。
#ifdef TESTING
#define test_main main
#else
#define real_main main
#endif

int test_main( int argc, char *argv[] ) { ... }
int real_main( int argc, char *argv[] ) { ... }

7

我假设你正在使用Make或类似工具。我建议创建两个文件,分别包含不同的主函数实现,然后在makefile中定义两个独立的目标,这两个目标都依赖于除了一个使用“单元测试主函数”和另一个使用“普通主函数”之外的相同文件。示例如下:

normal: main_normal.c file1.c file2.c
unittest: main_unittest.c file1.c file2.c

只要“正常”目标更接近 makefile 的顶部,那么键入“make”将默认选择它。要构建测试目标,您需要键入“make unittest”。

4
我更喜欢采用这种方法,而不是试图将两个主程序挤入同一个主例程,并使用预处理器定义或链接器选项在它们之间进行切换。 - Owen S.

6

我倾向于在测试和生产环境中使用不同的文件和构建方式,但如果你确实有一个包含

testing

production

代码的文件,你可以使用条件语句来区分它们。

int test_main (int argc, char*argv[])

并且

int prod_main (int argc, char*argv[])

然后选择一个作为主要的编译器选项是-Dtest_main=main-Dprod_main=main


2
也许值得指出的是,如果由于某种原因无法重命名生产文件中的主函数(就像我遇到的情况一样),则可以在编译生产主函数时在-D开关中交换符号名称,即将其设置为“-Dmain=ignored_main”。 - tomger

5

今天我也遇到了同样的问题:m1.c和m2.c都有一个main函数,但需要将它们链接并运行其中之一。 解决方法:在编译之后但在链接之前,使用STRIP从其中一个文件中删除main符号:

gcc -c m1.c m2.c;  strip --strip-symbol main m1.o; gcc m1.o m2.o; ./a.out

将从m2运行主程序

gcc -c m1.c m2.c;  strip --strip-symbol main m2.o; gcc m1.o m2.o; ./a.out

将从m1中运行主函数

不去除标签:

gcc - m1.c m2.c
m2.o: In function `main':
m2.c:(.text+0x0): multiple definition of `main'
m1.o:m1.c:(.text+0x0): first defined here
collect2: ld returned 1 exit status

2
编辑: Billy已经回答了,但这里提供更多背景信息。
更直接地说,main通常更多是标准库的一个函数。调用main的不是C,而是标准库。操作系统加载应用程序,将控制权转移到库的入口点(GCC中的_start),最终库调用main。这就是为什么Windows应用程序的入口点可以是WinMain而不是通常的main。嵌入式编程也可能有类似的情况。如果没有标准库,您必须编写库通常提供的入口点(以及其他内容),并且可以按任意名称命名它。
在GCC工具链中,您还可以使用-e选项将库的入口点替换为自己的入口点。(实际上,您还可以完全删除库。)
自己动手做:
int main(int argc, char *argv[])
{
#if defined(BUILD_UNIT_TESTS)
    return main_unittest(argc, argv);
#endif
#if defined(BUILD_RUNTIME)
    return main_run(argc, argv);
#endif
}

如果您不喜欢ifdef,那么可以编写两个仅包含主函数的主模块。其中一个用于单元测试,另一个用于正常使用。


1
如果您使用LD "-e symbol_name"(其中symbol_name是您的主函数,当然),您还需要"-nostartfiles",否则将会产生"undefined reference to main"的错误。

3
我认为这不会解决 OP 的问题。绕过启动文件会导致您处于一个破损的环境中,其中标准库将无法正常工作。我认为只需要使用“-Dmain”和类似的选项就可以了。 - R.. GitHub STOP HELPING ICE

0
#ifdef TESTING

int main()

{

/* testing code here */

}

#else

int main()

{

/* normal code here */

}

#endif

$ gcc -DTESTING=1 -o a.out filename.c #用于测试编译

$ gcc -UTESTING -o a.out filename.c #正常目的编译

man gcc 告诉我关于 -D 和 -U 的信息


1
唯一的缺点是你需要将整个 main 函数的代码放在一个 define 中... 一些 IDE 的语法高亮会将所有测试代码变灰(因为它通常不被定义),这可能会变得非常烦人。 - Billy ONeal

0

可能 需要单独运行 ld 来使用它们,但是 ld 支持 脚本 来定义输出文件的许多方面(包括入口点)。


但是 main 不是入口点(至少在大多数系统上不是)。 - Carl Norum

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接