C 为什么我会得到';多重定义';针对存档链接时出错?
我使用CppUTest测试C 为什么我会得到';多重定义';针对存档链接时出错?,c,unit-testing,linker,cpputest,C,Unit Testing,Linker,Cpputest,我使用CppUTest测试fornol.C源文件中定义的C代码。该文件定义了主产品main()函数 我还有一个AllTests.cpp文件,它也有一个main()函数,但是main()应该只在运行单元测试时使用 AllTests.cpp被编译成.o文件,而fornol.c被编译成libfornol.a归档文件 然后CppUTest尝试将所有内容链接在一起,但我得到的是: Linking fornol_tests cc -o fornol_tests objs/tests/AllTests.
fornol.C
源文件中定义的C代码。该文件定义了主产品main()
函数
我还有一个AllTests.cpp
文件,它也有一个main()
函数,但是main()
应该只在运行单元测试时使用
AllTests.cpp
被编译成.o
文件,而fornol.c
被编译成libfornol.a
归档文件
然后CppUTest尝试将所有内容链接在一起,但我得到的是:
Linking fornol_tests
cc -o fornol_tests objs/tests/AllTests.o objs/tests/FornolTests.o lib/libfornol.a ../../CppUTest/lib/libCppUTest.a ../../CppUTest/lib/libCppUTestExt.a -lstdc++ -lgcov
lib/libfornol.a(fornol.o): In function `main':
/home/dlindelof/Work/endor/nol/fornol/fornol.c:453: multiple definition of `main'
objs/tests/AllTests.o:/home/dlindelof/Work/endor/nol/fornol/tests/AllTests.cpp:4: first defined here
collect2: ld returned 1 exit status
在fornol.c
中定义并存在于存档libfornol.a
中的main()
函数似乎与AllTests.cpp
中定义的main()
函数冲突。但我的理解是,只有在给定符号尚未被引用时,才会搜索存档/库文件。因此,如果所有定义都在存档/库文件中,则多次定义同一符号应该不会有问题
我做错了什么?库不应该有main()函数,因为它是一个库 您应该从fornol.c中删除main()并重新编译它 main()是可执行文件源代码的入口点,因为库(尤其是static“.a”库)只是预编译的源代码,所以不能在其中使用main 如果需要库的主产品入口点,可以将fornol.c中的main()重命名为更明确、保留更少的名称,例如“fornolMain()”
静态库在二进制可执行文件中编译,因此仅在加载符号时才会搜索。这与编译fornol.c并链接fornol.o和其他.o完全相同您需要从
AllTests.cpp
中删除main()
,并将其放入自己的源文件中
链接器在库中链接时,不能拆分库中的对象文件;它必须作为一个单元链接或省略库中的每个对象文件。(我知道LLVM是不同的,但这是另一个主题。)这就是为什么,如果您查看像glibc这样的库的源代码,每个函数都有自己的源文件
因此,发生在您身上的是,链接器需要从库(libfornol.a
)中拉入一个对象文件(fornol.o
),以满足依赖关系,但该对象文件带有一个重复的符号(main
)
将测试代码放在库中(我们在我工作的地方经常这样做),但将其保存在自己的源文件中(我们传统上使用
main.cc
)。(无论如何,这是一个更好的测试,因为测试代码不应该访问static
-声明的符号。)我没有拆分AllTests.cpp
,而是将fornol.c
拆分为一个main.c
部分,该部分包含main()
函数和fornol.c
,其中包含所有其他内容,这在一定程度上有所帮助。libfornol.a
存档现在由两个.o
文件组成,我可以使用AllTests.cpp
中的main()
函数。但我现在不明白的是,gcc允许我在AllTests.cpp
中重写仍在fornol.c
中定义的函数。那么为什么main()
会受到不同的对待呢?您误解了链接器。它不考虑功能或覆盖。它从寻找缺失符号的角度来思考。每个对象文件都有其提供的符号和所需的符号列表。链接器首先遍历给定给它的对象文件,并将一个对象文件提供的符号与另一个对象文件所需的符号进行匹配。完成后,它会有一个仍然丢失的符号列表,并按顺序遍历每个库,查找提供这些符号的对象。但是,如果一个提供符号的对象也提供了一个已经提供的符号,那么它就失败了;“printf”也可能发生同样的情况。链接器会继续这个过程,直到它遇到错误或者不再需要符号。哦,我想我现在可以试试了。感谢您澄清这样一个事实:对象文件要么是完全链接的,要么根本没有链接。你有没有什么资源可以推荐给我,让我在那里可以阅读更多关于链接工作原理的信息?GNUld
文档是我能想到的全部。这就是我十年前读到的。有一件事我忘了提到:GCC有一个选项(-fffunction sections
,IIRC),它将每个函数放在对象文件中单独的“部分”中。将类似的标志传递给链接器允许它使用节粒度而不是对象文件粒度。这适用于对象文件和库。