0.COMPILE

  2021-8-14 


COMPILE

编译器

gccg++

gcc和g++的区别:gcc不会主动链接标准库STL等 ,不主动link就只能编译单个源文件;gcc也是调用了g++里的cc1plus;但gcc更稳定

gcc和g++编译程序发生了什么:

preprocess:调用预处理器(cpp)生成预处理文件(.i、.ii文件,后者是cpp) 【源代码——>预处理源代码】

gcc -E hello.cpp -o hello.ii

compile:调用实际的编译器(如cc cc1 cc1pp)生成汇编代码(.s文件) 【预处理后源代码——>汇编代码】

gcc -S hello.ii -o hello.s

assemble:调用汇编器(如as),生成目标文件(.o/.a文件) 【汇编代码——>机器指令】

gcc -c hello.s -o hello.o

link:调用链接器(如ld),链接多个目标文件(链接库),生成可执行文件 【链接多个机器指令.o文件/.a文件,生成可执行文件】

gcc hello.o -o hello

对于多个链接目标gcc a.o b.o c.o ..... -o output

也可以源文件与链接库混编(合并编译)gcc main.cc f1.o f2.o f3.a ... -o output

即:

-E                       Preprocess only; do not compile, assemble or link.
-S                       Compile only; do not assemble or link.
-c                       Compile and assemble, but do not link.   
-o <file>                Place the output into <file>.

注意,上面的参数是向前兼容的,比如可以gcc -c hello.cpp -o hello.o直接由源文件生成目标文件

[-c是非常常用的,先preprocess、compile、assemble,就可以先编译生成第三方库的链接库文件了。

-o指定输出目标(各阶段都可以用)

【注意1】:gcc在链接过程中,对参数中的库的顺序是有要求的,参数【右侧的库会先于左侧的库加载】,也就是说参数的解析是从右往左的

一般报错undefined reference就是这个顺序问题导致的

【注意2】:gcc不自动链接标准库,所以需要手动链接:【添加参数-lstdc++

编译的时候可添加-I(添加搜索头文件的路径,见下面声明第三方库的方法1)、-L(指定要链接库所在的目录;放在/lib/usr/lib/usr/local/lib里的库直接用-l参数就能链接了,我们可以直接把第三方库放进去,如果不在上述三个默认目录,需要指定链接库目录)、-l(指定路径寻找库文件;直接跟库名,比如库文件叫test,则参数为-ltestP.S.链接库格式为libxxxx,自己生成的链接库如果要合并编译也要遵守这个命名规则

P.S. 通过 -l -L来指定静态链接库,和直接链接链接库效果是一样的,即gcc a.o b.o -o main=gcc a.o -lb -L . -o main(b链接库改名libb.a),但是对于动态链接库,就必须再用-l -L的方式来编译!(P.S.2 在同时有同名的静态和动态库的情况下,gcc默认是选择动态库的)


Cpp程序的编译,链接过程:

链接的时候就必须要有个main()函数作为可执行程序的入口了

alpha.c(main) -> alpha.obj(编译)
                  	   	    	\
                     	     	program.exe(链接)
                     	    	/
		beta.c  -> beta.obj(编译)

这个.obj结尾的文件就是目标文件,也就是链接库静态链接库.a.lib,动态链接库.so.dll

静态库:在链接阶段,会将汇编生成的目标文件.o与引用到的库一起链接打包到可执行文件中。在编译时期完成,之后再与函数库无瓜葛,很方便移植,但浪费空间

动态库:动态库在程序编译时并不会被链接到目标代码中(跳过链接这一步),而是在程序运行是才被载入。不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例节省空间,且可以直接对函数库进行修改,无须重新编译


P.S. gcc9.3完全支持到C++17,默认支持c++14,要使用c++17需要gcc -std=c++17

第三方库

Cpp使用第三方库需要

①include第三方库的头文件(.h),以使得编译器能够对源代码进行编译(得要声明第三方库才能使用库函数)

②链接第三方库的链接库文件(第三方库编译生成的目标文件.a.lib,动态链接库则运行时装载.so.dll),以生成可执行文件

编译的时候

第三方库文件有两种情况:

使用第三方源代码大致分为两种情况:

  1. 第三方库不大有源代码,但并未提供二进制库文件。比如jsoncpp 这种轻巧的库

    对于这种就需要和自己的源代码一起进行编译。需要在引用第三方库功能的源代码头部include 第三方代码的头文件,并将源代码的实现部分放置在自己的源代码文件夹中。构建时让C++ 编译器一起编译成二进制代码,再链接到一起。

  2. 第三方库代码庞大或者仅提供头文件和编译好的二进制文件

    对于像openssl、ffmpeg 等等大块头的开源项目,即使是拿到了源代码也不建议将其与自己的源代码合并编译。因为他们本身就很复杂且搭建编译环境也要费一番功夫。一般这些项目都提供已经编译好的二进制库文件。极端情况下仅有源代码文件没有库文件,就需要先配置这些项目的编译环境先编译输出二进制库文件。

    这些项目提供的已经编译好的库文件.so .o .lib .dll 等对应的头文件配合使用include到自己的源代码项目中。使用时,将第三方头文件include 到使用这些第三方库的源代码中,然后在链接环节指定链接器链接这些已经编译好的二进制库文件。其实在一些自动化构建工具比如CMake 中,这些非常好指定。

链接:https://www.zhihu.com/question/384875984/answer/1173406367

总的来说,就是库文件编译好没有的区别,大库用CMake构建指定头文件与编译好的库文件路径,小库则直接将源码放目录下即可

P.S.声明第三方库才能使用库函数,但为什么Cpp不像其它高级语言一样声明库函数后自己去库函数源文件找声明,而要单独一个.h头文件来声明呢?这是历史原因:为什么C/C++要分为头文件和源文件? - wzsayiie的回答 - 知乎 总结:和Cpp一起编译的其它文件可能根本不是Cpp,所以得自己单独声明

Cpp提供了标准库,可以直接通过#include <library name>声明不用加路径,其存放于/usr/include/<ver>/<libaray name>,对于C提供的库(非标准库),通过#include "<library name>"文件来声明,注意C++标准库的文件是无后缀的,且用<>来include,其它库是.h头文件后缀,非C提供的库的其它第三方非标准库则需要声明时写出库头文件存放的位置


为了方便声明第三方库,可以

添加编译选项-I

②修改系统环境变量(注意,修改了环境变量,gcc/g++的时候还是要声明link的头文件!只是不用写路径了)

将头文件路径和库文件路径添加到指定的系统环境变量中去,具体如下:

  • 使用 gcc 编译时将 头文件 路径添加到 C_INCLUDE_PATH 系统环境变量中;
  • 使用 g++ 编译时将 头文件 路径添加到 CPLUS_INCLUDE_PATH 系统环境变量中;
  • 动态连接库 路径添加到 LD_LIBRARY_PATH 系统环境变量中;
  • 静态库 路径添加到 LIBRARY_PATH 系统变量中。

改变系统变量主要有两种形式,一种是临时改变,另一种是永久改变。

临时改变系统变量只需要使用 export 命令,重启终端后将恢复至先前状态,如

>export C_INCLUDE_PATH=$CPLUS_INCLUDE_PATH:/myinclude1

而永久改变又可分为仅改变当前用户和改变所有用户:

  • 仅改变当前用户的系统环境时,只需将上述的 export 语句添加到 ~/.bashrc 或者 ~/.bash_profile 文件内容后面,重启终端即可。
  • 要改变所有用户的环境变量时,需将上述的的 export 语句添加到 /etc/profile 文件内容,并需要重启计算机。

链接:https://www.codeleading.com/article/53332291471/

P.S. 环境变量存放于~/.bashrc (当前用户环境变量)、 ~/.bash_profile (所有用户环境变量)

pkg-config命令对于第三方库常用,不然自己一个个找需要哪些系统链接库非常痛苦)

部分第三方库在安装完成后会自动生成一个 .pc 的配置文件用来存放第三方库安装的路径,包括头文件路径以及库文件路径,可以使用 *pkg-config 命令查看,如:

>pkg-config --cflags --libs protobuf

>>>> -pthread -I/usr/local/include -L/usr/local/lib -lprotobuf -pthread123

其中:

  • –cflags 返回头文件目录
  • –libs 返回库所在的目录以及库参数
  • protobuf 为第三方库名

在编译包含这些库的代码时,需要执行上述命令获取返回结果,比如:

>g++  main.c `pkg-config --cflags --libs protobuf`

其中,`符号为数字1键左边的那键。

对于某些三方库没有.pc文件,我们也可以自己写一个配置文件。.pc文件的路径存放在 PKG_CONFIG_PATH 系统变量下,使用 echo 命令可查看其变量。

>echo $PKG_CONFIG_PATH

>>>> :/usr/local/lib/pkgconfig123

进入该路径,我们可以看到 *.pc 的配置文件,用文本编辑器打开其中一个如protobuf.pc,其内容如下:

>prefix=/usr/local
>exec_prefix=${prefix}
>libdir=${exec_prefix}/lib
>includedir=${prefix}/include

>Name: Protocol Buffers
>Description: Google's Data Interchange Format
>Version: 3.5.2
>Libs: -L${libdir} -lprotobuf -pthread
>Libs.private: -lz
>Cflags: -I${includedir} -pthread
>Conflicts: protobuf-lite123456789101112

其中:

  • libdir 为库所在目录
  • includedir 为头文件所在目录
  • Libs 为调用参数,其内容为库所在的目录以及库参数
  • cflags 为调用参数,其内容为头文件目录

因此,我们可以照着上面的形式自己写一个.pc文件放到 *PKG_CONFIG_PATH 所指定的目录下。

链接:https://www.codeleading.com/article/53332291471/

当然也可以直接写全路径

对于同一个项目声明自己的其它函数库,就直接用相对路径引用即可

工程构建:makecmake

  1. make 是用来执行Makefile的
  2. Makefile是类unix环境下(比如Linux)的类似于批处理的”脚本”文件。其基本语法是: 目标+依赖+命令,只有在目标文件不存在,或目标依赖的文件更旧,命令才会被执行。由此可见,Makefile和make可适用于任意工作,不限于编程。比如,可以用来管理latex。
  3. Makefile+make可理解为类unix环境下的项目管理工具,但它太基础了,抽象程度不高,而且在windows下不太友好(针对visual studio用户),于是就有了跨平台项目管理工具cmake
  4. cmake是跨平台项目管理工具,它用更抽象的语法来组织项目。虽然,仍然是目标,依赖之类的东西,但更为抽象和友好,比如你可用math表示数学库,而不需要再具体指定到底是math.dll还是libmath.so,在windows下它会支持生成visual studio的工程,在linux下它会生成Makefile,甚至它还能生成eclipse工程文件。也就是说,从同一个抽象规则出发,它为各个编译器定制工程文
  5. cmake是抽象层次更高的项目管理工具,cmake命令执行的CMakeLists.txt文件
  6. qmake是Qt专用的项目管理工具,对应的工程文件是*.pro,在Linux下面它也会生成Makefile,当然,在命令行下才会需要手动执行qmake,完全可以在qtcreator这个专用的IDE下面打开·*.pro文件,使用qmake命令的繁琐细节不用你管了。

总结一下,make用来执行Makefile,cmake用来执行CMakeLists.txt,qmake用来处理.pro工程文件。Makefile的抽象层次最低,cmake和qmake在Linux等环境下最后还是会生成一个Makefile。cmake和qmake支持跨平台,*cmake的做法是生成指定编译器的工程文件,而qmake完全自成体系。

make链接:https://www.zhihu.com/question/27455963/answer/36722992

make install: 将 make 生成的文件安装到系统的对应目录中。

其中主要是bin和lib文件,将编译好的可执行文件和库文件导入系统对应目录,就可以正常使用了

cmake的用法

CMakeLists 的使用,大型工程使用cmake 的构件过程

CMake入门和大型工程管理

  1. 编译库

    将右边所有指定源文件(source1,source2…..)编译生成左边名字为<name>的目标文件(链接库)

    STATIC:生成静态库(.o);SHARED:生成动态库(.so)

    add_library(<name> [STATIC | SHARED | MODULE]
                [EXCLUDE_FROM_ALL]
                [source1] [source2] [...])
  2. 将目标文件与库文件进行链接

    其中<target>指通过add_executable()add_library()指令生成已经创建的目标文件(前者就是动态链接,后者就是静态链接),[item]表示库文件没有后缀的名字。

    target_link_libraries(<target> [item1] [item2] [...]
                          [[debug|optimized|general] <item>] ...)

    target_link_libraries里库文件的顺序符合gcc链接顺序的规则,即被依赖的库放在依赖它的库的后面

  3. 添加头文件搜索路径

    使用了这个之后,在源文件中, #include就不需要再写头文件目录了

    include_directories(
    	path1...
    	path2...
    )
  4. 用来显式的定义变量

    定义变量name代替后面的字符串

    set(<name> xxx1 
    	xxx2
    	xxx3)
  5. 生成可执行文件

    add_executable (<name> [WIN32] [MACOSX_BUNDLE]
          [EXCLUDE_FROM_ALL]
          [source1] [source2 ...])
    target_sources(<name> [WIN32] [MACOSX_BUNDLE]
          [EXCLUDE_FROM_ALL]
          [source1] [source2 ...])

    target_sources继续为可执行目标文件添加源文件,要求是在调用target_sources之前,可执行目标文件必须已经通过add_executable或add_library定义了。

  6. 添加一个子目录并构建该子目录。

    大型cpp工程,多层次cmake使用,通过这个指令可以链接多个子模块

    add_subdirectory (source_dir [binary_dir] [EXCLUDE_FROM_ALL])

    source_dir
    必选参数。该参数指定一个子目录,子目录下应该包含CMakeLists.txt文件和代码文件。子目录可以是相对路径也可以是绝对路径,如果是相对路径,则是相对当前目录的一个相对路径。

    binary_dir
    可选参数。该参数指定一个目录,用于存放输出文件。可以是相对路径也可以是绝对路径,如果是相对路径,则是相对当前输出目录的一个相对路径。如果该参数没有指定,则默认的输出目录使用source_dir

    EXCLUDE_FROM_ALL
    可选参数。当指定了该参数,则子目录下的目标不会被父目录下的目标文件包含进去,父目录的CMakeLists.txt不会构建子目录的目标文件,必须在子目录下显式去构建。例外情况:当父目录的目标依赖于子目录的目标,则子目录的目标仍然会被构建出来以满足依赖关系(例如使用了target_link_libraries)

    详见:https://www.jianshu.com/p/07acea4e86a3

  7. 配置(PROPERTIES有很多种,见doc)

    set_target_properties(target1 target2 ...
                          PROPERTIES prop1 value1
                          prop2 value2 ...)

    https://cmake.org/cmake/help/latest/command/set_target_properties.html

  8. PROJECT标识

    PROJECT(<project_name>)

    配合下面的PROJECT_SOURCE_DIR内置变量使用

  9. cmake内置变量

    CMAKE_SOURCE_DIR指定了CMakeLists.txt所在的目录

    PROJECT_SOURCE_DIR为包含PROJECT()的最近一个CMakeLists.txt文件所在的文件夹

另外,bash中的${VAR}参数为脚本环境变量,若脚本名为ex.sh,则可以VAR=XXX ./ex.sh来设置运行时环境变量

make的用法就不用管了,cmake生成了Makefile之后,直接makemake install就完事

安装gcc11

git clone --branch releases/gcc-11.1.0 https://github.com/gcc-mirror/gcc.git

#安装依赖
./contrib/download_prerequisites
#配置安装目录
./configure --prefix=/lib/gcc/x86_64-linux-gnu/11/  --enable-bootstrap --enable-languages=c,c++ --enable-threads=posix --enable-checking=release --enable-multili│
b --with-system-zlib
# 我是安装在/usr/lib/gcc/x86_64-linux-gnu/11 (以前的gcc9安装在/usr/lib/gcc/x86_64-linux-gnu/9)
#编译
make -j112
#安装
make install
#安装好之后设置软连接(先删掉原来指向gcc9的软连接)
sudo ln -s /usr/lib/gcc/x86_64-linux-gnu/11/bin/gcc /bin/gcc
sudo ln -s /usr/lib/gcc/x86_64-linux-gnu/11/bin/g++ /bin/g++

可能出的错:

  1. 编译时:gcc: error: gengtype-lex.c: No such file or directory

    解决:sudo apt install flex

  2. 编译时:/usr/include/stdio.h:27:10: fatal error: bits/libc-header-start.h: No such file or directory

    解决:sudo apt install gcc-multilib

  3. 安装好后运行可能报错

    /lib/x86_64-linux-gnu/libstdc++.so.6: version GLIBCXX_3.4.29' not found

    这是因为系统中的标准动态链接库还未更新到gcc11要求的最新的版本

    系统链接库存放位置:/lib/x86_64-linux-gnu

    gcc11的64位链接库位置:gcc目录下:x86_64-linux-gnu/11/lib64

    #原来的
    ls /lib/x86_64-linux-gnu -lh|grep libstdc++
    
    lrwxrwxrwx  1 root root    19 May 29 07:49 libstdc++.so.6 -> libstdc++.so.6.0.28
    -rw-r--r--  1 root root  1.9M May 29 07:49 libstdc++.so.6.0.28
    
    #复制过去并建立软连接,让libstdc++.so.6链接最新版本
    cp /usr/lib/gcc/x86_64-linux-gnu/11/lib64/libstdc++.so.6.0.29 /lib/x86_64-linux-gnu/
    sudo ln -s /lib/x86_64-linux-gnu/libstdc++.so.6.0.29  /lib/x86_64-linux-gnu/libstdc++.so.6
    ls /lib/x86_64-linux-gnu -lh|grep libstdc++
    
    lrwxrwxrwx  1 root root    19 Sep  2 09:50 libstdc++.so.6 -> libstdc++.so.6.0.29 #new
    -rw-r--r--  1 root root  1.9M May 29 07:49 libstdc++.so.6.0.28 #old
    -rwxr-xr-x  1 root root   18M Sep  2 09:48 libstdc++.so.6.0.29 #new
    lrwxrwxrwx  1 root root    19 May 29 07:49 libstdc++.so.6.backup -> libstdc++.so.6.0.28 #old
    
    #参考:https://www.linuxidc.com/Linux/2017-10/147621.htm

    问题:需要把build出来的的bin、lib的东西全部倒腾到系统对应目录下

Extra

  1. 注意,要在源文件中对自己的头文件进行声明

  2. YCM语法检查是看头文件是否存在而不是看库是否存在,如果库不在是报链接库找不到的错误

  3. 父目录CMakeList的变量,可由子目录直接引用

  4. 关于头文件查找:

    <> 先到系统默认include目录(/usr/include)下查找,找不到再去工程目录下的 include 文件夹(需先指定工程目录,比如用cmake的话默认工程目录就是PROJECT_SOURCE_DIR)下查找。(不会在当前目录下查找

    "" 先在当前目录下查找,找不到的情况下,再到安装目录的 include 文件夹下查找,再找不到才去系统默认include目录下查找

    如果直接用gcc使用第三方库头文件,要么写清相对绝对路径,要么gcc -I,要么加环境变量

    注意:YCM是不会去其它目录寻找头文件的,所以别开YCM语法检查;或者为每个项目单独配置一个.ycm_extra_conf.py

  5. “undefined reference to” 多种可能出现的问题解决方法

    链接问题


且听风吟