浅谈如何使用clang替换gcc进行编译

经过多年的发展,LLVM事实上已经对大部分语言进行了支持,其完备的功能和好的模块化和轻耦合的特性得到了很多人的认可,但是在很多传统领域,实际上的编译器还是gcc(基础设施),大部分人如果想使用LLVM对gcc进行替换时,会遇到一些麻烦。Clang的官网上对这个地方有个说明:The 'clang' driver is designed to work as closely to GCC as possible to maximize portability. The only major difference between the two is that Clang defaults to gnu99 mode while GCC defaults to gnu89 mode. If you see weird link-time errors relating to inline functions, try passing -std=gnu89 to clang.。本文想从一个普通开发者的角度去解释一些传统gcc编译领域使用clang来进行替换的一些实际步骤。

我想根据project的特点,把LLVM替换gcc的编译过程分为以下三个类型(不完全是,简单分类):

  1. 测试型的project,例如自己写的testcase,基本上以一个main函数,调用几个简单的cpp文件为主,使用shell脚本进行编译
  2. 传统的使用configure生成Makefile文件,然后进行编译的project
  3. 大型project,往往使用cmake生成编译脚本,然后编译的project

1. 测试型的project

    对于这种类型,一般就是几条简单的gcc或者g++编译命令,这种project建议完全复制,粘贴时将gcc替换为clang就能解决问题,大部分gcc支持option,clang都进行了支持,甚至支持的更好,一般用户很少能写出gcc支持而clang不支持的命令

2. 传统configure类型的project

    对于这种类型的project,是目前一般用户见到的最多的。我这里以一个开源的bird-2.0.8来进行说明。(首先说明,非网络专业用户,仅测试使用)某度百科上的词条介绍是:BIRD是一个类UNIX系统的动态路由守护进程。它支持当代互联网中所用所有路由协议,如BGP、OSPF、RIP和这些协议的IPv6的变种(除OSPFv3目前尚在发展)。其实做什么的,我们并不关心,我们只关心如何得到一个正确的编译结果。

    要想知道如何得到一个正确的结果,正常使用gcc搞这类project的流程是使用configure进行配置,然后make,后边make install进行安装,其中configure是用来生成Makefile文件的,也会进行很多配置的检测。这里我采用的思路是首先用默认配置来一遍,然后再换编译器。

从这里路径下载代码后,解压进入source文件夹。https://bird.network.cz/download/bird-2.0.8.tar.gz

./configure

会遇到

 的问题,按照提示安装libreadline或者直接使用

./configure --disable-client
make -j 32

就可以发现编译完成了,非常简单。

现在我们开始换编译器。先看下configure文件,发现有如下的介绍:

也就是说刚才在配置的时候,通过设置CC,CPP等环境变量就可以实现切换编译器的过程,我这里采用最暴力的方法,直接替换(其实也差不了太多)。

找到Makefile文件,查找CC的变量直接替换为CC=clang, 然后make,发现问题:

LD -pthread -flto=4 -g -o bird obj/conf/cf-parse.tab.o obj/conf/cf-lex.o obj/conf/conf.o obj/filter/filter.o obj/filter/data.o obj/filter/f-util.o obj/filter/tree.o obj/filter/trie.o obj/filter/inst-gen.o obj/lib/bitmap.o obj/lib/bitops.o obj/lib/checksum.o obj/lib/event.o obj/lib/flowspec.o obj/lib/idm.o obj/lib/ip.o obj/lib/lists.o obj/lib/mac.o obj/lib/md5.o obj/lib/mempool.o obj/lib/net.o obj/lib/patmatch.o obj/lib/printf.o obj/lib/resource.o obj/lib/sha1.o obj/lib/sha256.o obj/lib/sha512.o obj/lib/slab.o obj/lib/slists.o obj/lib/strtoul.o obj/lib/tbf.o obj/lib/timer.o obj/lib/xmalloc.o obj/nest/a-path.o obj/nest/a-set.o obj/nest/cli.o obj/nest/cmds.o obj/nest/iface.o obj/nest/locks.o obj/nest/neighbor.o obj/nest/password.o obj/nest/proto.o obj/nest/rt-attr.o obj/nest/rt-dev.o obj/nest/rt-fib.o obj/nest/rt-show.o obj/nest/rt-table.o obj/proto/bfd/bfd.o obj/proto/bfd/io.o obj/proto/bfd/packets.o obj/proto/babel/babel.o obj/proto/babel/packets.o obj/proto/bgp/attrs.o obj/proto/bgp/bgp.o obj/proto/bgp/packets.o obj/proto/mrt/mrt.o obj/proto/ospf/dbdes.o obj/proto/ospf/hello.o obj/proto/ospf/iface.o obj/proto/ospf/lsack.o obj/proto/ospf/lsalib.o obj/proto/ospf/lsreq.o obj/proto/ospf/lsupd.o obj/proto/ospf/neighbor.o obj/proto/ospf/ospf.o obj/proto/ospf/packet.o obj/proto/ospf/rt.o obj/proto/ospf/topology.o obj/proto/perf/perf.o obj/proto/pipe/pipe.o obj/proto/radv/packets.o obj/proto/radv/radv.o obj/proto/rip/packets.o obj/proto/rip/rip.o obj/proto/rpki/rpki.o obj/proto/rpki/packets.o obj/proto/rpki/tcp_transport.o obj/proto/rpki/ssh_transport.o obj/proto/rpki/transport.o obj/proto/static/static.o obj/sysdep/linux/netlink.o obj/sysdep/unix/io.o obj/sysdep/unix/krt.o obj/sysdep/unix/log.o obj/sysdep/unix/main.o obj/sysdep/unix/random.o
clang-7: error: unsupported argument '4' to option 'flto='
Makefile:159: recipe for target 'bird' failed
make: *** [bird] Error 1

我这里使用的LLVM 7.0,所以会提示clang-7,后边意思就是clang-7不支持gcc的-flto=4的option,man gcc查看该option的用法,是一个用于link级别的选项,再man clang就能发现clang在这个选项,要求使用 -flto -O2这种标准的用法,这里编译替换就好:

CFLAGS=$(CPPFLAGS) -g -O2 -pthread -fno-strict-aliasing -fno-strict-overflow -flto -Wall -Wextra -Wstrict-prototypes -Wno-parentheses -Wno-pointer-sign -Wno-missing-field-initializers
LDFLAGS= -pthread -flto -O2 -g

既然是LD提示的错误,直接给LDFLAG修改就好。

再次编译,会出现/usr/bin/ld: /home/daily_learning/oldLLVM/build/bin/../lib/LLVMgold.so: error loading plugin: /home/daily_learning/oldLLVM/build/bin/../lib/LLVMgold.so: cannot open shared object file: No such file or directory

也就是LLVMgold.so找不到的问题,到那个文件下,发现确实没有这个so文件,这是因为加了flto选项需要引入LLVMgold库,这个库是需要放在源码中进行编译的,网上的教程很多,不再赘述,解决掉这里后,发现就能得到和gcc一样的结果。整个替换过程比较简单。

相对直接用于test的project来说,一般project的configure和编译过程相对比较复杂,整个编译流程也分为了预编译、编译、链接三个过程,预编译一般没有问题,编译过程中可能遇到option的问题,链接过程可能遇到库的兼容性问题,相对来说,有点复杂也需要一点经验。但是对于想入手学习的人来说,还是非常有必要的。

对于想要进行学习交流的来说,很多时候需要获得的不是最终的链接结果,需要的是中间的bc文件,这个时候其实完全不需要考虑什么-c的编译过程中添加什么-emit-llvm什么选项这些复杂的问题,直接在CFLAGS后边添加-save-temps,你就可以获得中间文件了,虽然有点多。

3. 大型project

   这部分其实和上边差不多,不过这种功能一般就不能暴力修改Makefile文件来进行了,需要预先配置编译器和环境变量。我这里给出一个我使用的环境变量,其他人可以对照修改(不保证完全够):

export LLVM_HOME=/home/daily_learning/oldLLVM
export PATH=/home/local/bin:$LLVM_HOME/build/bin:$PATH
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$LLVM_HOME/build/lib
export C_INCLUDE_PATH=$C_INCLUDE_PATH:$LLVM_HOME/build/include
export CPLUS_INCLUDE_PATH=$CPLUS_INCLUDE_PATH:$LLVM_HOME/build/include

 说实话,非常想给出一个完整的例子。主要问题是,一个真实的project,想要全流程的介绍和切换,一般是稍微需要一点时间和耐心的,而且在短的篇幅内把遇到的问题都说明白,也非常考验project自身,所以找一个合适的project非常重要,待有机会再进行补充这部分内容。

原文地址:https://www.cnblogs.com/jourluohua/p/15004708.html