linux下的c++编译

1. 关于GCC

目前在linux上,C++的编译主要依赖于GCC,而GCC实际上并不是一个编译器,其全称为GNU Compiler Collection,即GNU的编译器集合,可以通过它来编译C/C++/Object-C等各种语言的源码。
其中gcc是GCC中的GNU C Compiler(c编译用),g++即为GCC中的CNU C++ Compiler(c++编译用)。
gcc和g++的主要区别可以查看这位博主的文章:GCC与gcc,g++区别

1.1 使用g++编译cpp文件

材料:装了HelloWorld的一份cpp代码
工具:装了linux和GCC的一台机器
然后直接调用命令就可以完成编译了:

g++ -o hello hello.cpp 

其中-o用于指定输出文件名称,上面命令中即为hello,而后的参数为需要编译的源码。(不带-o参数也可,则输出文件默认为a.out)
调用./hello,大伙儿喜闻乐见的HelloWorld就打印出来了。
这是最基础的一种编译方式,在实际项目中,因为源码文件存在多个,也有需要包含的链接库/头文件,以及指定生成目标为动态链接库/静态链接库等等,所以会需要一些额外的参数,类似于

  1. -E 参数,普通编译的第一步,输出源文件的预处理结果(加载头文件,宏替换,条件编译等),示例:
g++ -E hello.cpp -o hell.e
  1. -S 参数,普通编译的第二步,执行代码中的语法检查和词法分析,检查正确后将其翻译为汇编代码。
g++ -S hello.e -o hello.s
  1. -c 参数,普通编译的第三步,将汇编代码转换成二进制代码。
g++ -c hello.s -o hello.o
  1. 无编译类型参数,普通编译的最后一步,将二进制代码与库文件等其他文件链接,生成最终目标文件。(注意这四个步骤中,GCC会自动检查之前步骤是否完成,若之前的步骤未完成,则自动执行对应的操作)
g++ hello.o -o hello
  1. -o 参数,指定当前编译过程中产生的目标文件名(就像上面的示例,另外文件后缀是随便写的,linux有啥要求)。
  2. -I 参数(大写i),指定包含的头文件所在目录,相对路径和绝对路径均可,存在多个目录时多次添加即可。
g++ hello1.cpp hello2.cpp -o hello -I ./../include1 -I ./../include2
  1. -L 参数,指定链接的库文件所在目录,同上。
  2. -l 参数(小写L),指定链接的库文件名称,默认lib前缀,也不需要后缀,如链接liblua.a,写做-llua即可。一般情况下优先使用静态库进行链接,若想指定链接的库为静态库或动态库,可使用-l:加上库文件全称即可,如链接liblua.a,写作-l:liblua.a。
g++ hello.cpp -o hello -L ./../lib1 -llua -L ./../lib2 -ljson -l:libtest.so
  1. ar -r参数,用于指定生成静态链接库。示例:
ar -r hello.a hello.o

值得一提的是,即便hello中存在main函数,对应静态库仍能正常生成。因为在其他编译过程中链接该静态库时,只会“按需”链接。
详细情况可以查看这位博主的博客:自定义函数与静态库函数重名不冲突
10. -shared 参数,用于指定生成动态链接库。示例:

g++ -o hello.so hello.cpp -shared -fPIC

部分系统在使用-shared参数时,是会自动补上-fPIC参数的,但也有些系统不会。
所以此处最好显式标注-fPIC项,表明需要让编译器产生与位置无关代码,即产生的代码中全部使用相对地址,不是用绝对地址。
不过某些情况下不加-fPIC也能编译生成动态库,但条件比较苛刻。
(快给我住手这根本就不是动态链接)
其他参数读者可自行查阅资料。

1.2 使用makefile编译工程

在实际工程中,每次编译工程时都重新输入GCC编译命令显然过于憨憨。
所以我们需要通过脚本的形式,简化编译这一工作。其中,makefile就是一种用于编译的特殊脚本。
makefile的语法规则比较简单,主要为:

目标:依赖项
	命令1(前面固定一个Tab缩进)
	命令2

比如在上个GCC使用示例中的hello.cpp编译,我们可以在makefile中这样编写:

all:hello

hello:hello.cpp
	g++ -o hello hello.cpp

这样,只需调用make all命令,就可以生成对应的hello可执行文件了。
直接调用make命令时,makefile默认只生成第一个目标文件即完成编译,若像上述中调用make all,则makefile会编译指定的目标all。
而目标all的依赖项为hello,makefile就会检查hello是否存在,或者hello是否存在对应的生成命令。
如此一来,当我们第二次调用make all命令时,makefile检查到hello文件已存在,而之后也没有需要执行的命令,就会直接返回。
实际工程中,为了方便重复编译,还会再增加clean目标:

clean:
	rm -fr *.o hello

这样只需要调用make clean即可清除上一次产生的目标文件与中间文件,每次编译的命令即可简化为make clean;make all。(大家可能注意到清除文件使用了linux的rm命令,事实上在makefile中是可以直接调用linux命令的)
但仅此而已,当工程项目中源文件较多时,再去一个个写每个文件的名称也是比较低效的。
对此我们可以使用makefile自带的后缀规则.xxx.yyy,GCC会自动将xxx后缀的文件视为源文件,yyy后缀的文件视为目标文件,执行统一的生成命令(其中$*代表匹配的文件名中除了后缀的部分):

.cpp.o:
	g++ -o $*.o -c $*.cpp 

这样,.cpp后缀的文件被匹配到时,对应.o后缀的目标文件生成规则就有了。不过单靠这一条还不能直接直接编译,因为编译器需要知道你的源文件在哪。关于这个,makefile提供了wildcard函数可以用于获取对应后缀的文件,再通过makefile的变量替换规则$(VAR:A=B),将文件后缀替换为.o,就能得到中间文件的名称了。如此一来,可编译任意个数cpp文件的makefile就完成了:

SRC = $(wildcard *.cpp)
SRC_OBJ = $(SRC:.cpp=.o)

all:$(SRC_OBJ)
	g++ -o target.out $(SRC_OBJ)

clean:
	rm -fr *.o target.out

.cpp.o:
	g++ -o $*.o -c $*.cpp 

(一般将编译的一二三步与第四步链接拆分为两批执行,多文件编译可通过多线程提高编译速度)

1.3 通过CMakeLists.txt生成makefile

虽然在上面的示例中,makefile的编写已经存在部分的优化,但某些情况下仍然会存在编译参数多、乱,无明确归类的问题。
于是大佬们又想出了通过编写简单、规范的CMakeLists.txt来生成makefile文件的方式降低编写难度。(套娃警告)
我们编写CMakeLists.txt时的主要目标是设置相关编译参数,剩余的编译步骤交由它自动生成。因此你大部分的操作实际上都是在设置各种参数。
如设置c/c++的编译器SET(变量名 对应值):

SET(CMAKE_C_COMPILER /usr/local/bin/gcc540)
SET(CMAKE_CXX_COMPILER /usr/local/bin/g++540)

类似于CMAKE_C_COMPILER这种变量名都是CMake提前预设的名称,如果你想设置一个自定变量,只需要名称不与他们相同即可。
设置CMake的版本要求(必需项):

cmake_minimum_required(VERSION 2.6)

设置头文件的包含目录:

INCLUDE_DIRECTORIES(./../include)

设置目标文件的编译参数:

ADD_DEFINITIONS(-g -Wall -fPIC -static-libstdc++ -static-libgcc)

设置库文件的包含目录与链接的目标库文件:

LINK_DIRECTORIES(./../lib)
LINK_LIBRARIES(lua json)

获取源文件,并设置当前的编译目标为生成库文件:

AUX_SOURCE_DIRECTORY(./ SRC)
ADD_LIBRARY(target_name SHARED ${SRC})

AUX_SOURCE_DIRECTORY函数用于获取某个目录下所有的文件,用于ADD_LIBRARY中(表示源码文件)。而根据其中的SHARED参数可确认产生的文件为动态链接库,最终生成的文件即为target_name.so了。
其FIND_PATH/STRING/EXISTS等自带函数大家可以自行查阅相关文件与用法。

End

关于Linux上c++的编译就暂时写到这,如果大家有在Win平台上VS编译工程的经验,应该也能感觉到,Linux编译与Win编译过程中,需要设置的参数都是类似的。
???

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章