对于初学者用Makefile来编译程序的时候,时不时会出现undefined reference的错误,除了真的没有相关函数或变量的定义的原因之外,还有可能是你Makefile里的编译命令写的顺序不对。
要解决这个顺序问题,首先应该了解一下链接器是如何解析符号引用的。
以下摘自《深入理解计算机系统第2版》:
在符号解析阶段,链接器从左到右按照它们在编译器驱动程序命令行上出现的相同顺序来扫描可重定位目标文件和存档文件。在这次扫描中,链接器维持一个可重定位目标文件的集合E,这个集合中的文件会被合并起来形成可执行文件,和一个未解析的符号(也就是引用了但没有定义的符号)集合U,以及一个在前面输入文件中已定义的符号集合D。初始地,E、U、D都是空的。
1.对于命令行上的每个输入文件f,链接器会判断f是一个目标文件还是一个存档文件。如果f是一个目标文件,那么链接器把f添加到E,修改U和D来反映f中符号定义和引用,并继续下一个输入文件。
2.如果f是一个存档文件,那么链接器就尝试匹配U中未解析的符号和由存档文件成员定义的符号。如果某个存档文件成员m,定义了一个符号来解析U中的一个引用,那么就将m加到E中,并且链接器修改U和D来反映m中的符号定义和引用。对存档文件中所有成员目标文件都反复进行这个过程,直到U和D都不再发生变化。在此时,任何不包含在E中的成员目标文件都被丢弃,而链接器将继续到下一个输入文件。
3.如果当链接器完成对命令行上输入文件的扫描后,U是非空的,那么链接器就会输出一个错误并终止。否则,它会合并和重定位E中的目标文件,从而构建输出的可执行文件。
不幸的是,这种算法会导致一些令人困惑的链接时错误,因为命令行上的库和目标文件的顺序非常重要。如果在命令行中,定义一个符号的库出现在引用这个符号的目标文件之前,那么引用就不能被解析,链接会失败。
最终得出一个结论:所要的链接的静态库应该放到命令的最后,因为解析静态库的时候不会将其所有已定义的符号加入到D中(除非之前U中包含静态库中所有已定义的符号)。