======================= **基础知识** =======================
程序语言经历了:机器语言-> 汇编语言 -> 高级语言;
而对于高级语言来讲,其中又有编译型语言(C++) 与 解释型语言(python);
其中 编译型语言直接把高级语言源码编译成二进制机器码,直接运行机器玛就行 ; 解释型语言保存源程序代码文件,运行时候再有解释器一条条解释执行;
目前常用的有:
Microsoft C++ Compiler:内嵌在Visual C++中,在windows平台下和编辑器的嵌合很好;
MinGW GCC:GCC全称是GNU Compiler Collection(就是GNU编译器套件),著名的开源多语言编译器,编译出的代码执行效率比普通编译器高不少,支持单文件编译,得到很多编辑器支持。 通常下载到的MinGW GCC都包含包含C编译器gcc-core、C++编译器gcc-g++和另外几种语言的编译器。它可以编译很多种编程语言C、C++、JAV、Fortran、Pascal、Object-C、Ada等语言。
具体的有:gcc是GCC中的C 编译器, g++是GCC中的C++编译器;gcc和g++并不是编译器,也不是编译器的集合,它们只是一种驱动器,根据参数中要编译的文件的类型,调用对应的GUN编译器而已
对于从源码(source code) 到最终的执行程序,要通过 预处理(-E),编译(-S),汇编(-c) ,链接等步骤实现;比如,用gcc编译一个c文件的话,会有以下几个步骤:
Step1:Call a preprocessor, like cpp. 预处理阶段,将各类include/宏展开;
Step2:Call an actual compiler, like cc or cc1; 编译阶段,语法检查,没问题以后生成汇编;
Step3:Call an assembler, like as. 汇编过程,将上面汇编代码转成二进制代码;
Step4:Call a linker, like ld; 将各个二进制代码进行链接,生成最终的程序;
其中编译器是可以更换的,所以更准确的说法是:gcc调用了C compiler,而g++调用了C++ compiler
++++++++++++++gcc 与 g++ 命令的差别+++++++++++++++++++++++++++++
一般来讲gcc 编译c 代码, g++ 编译c++ 代码; 但是二者并不是完全不通的,因为本质来讲都是调用gcc ,对不同后缀选择不同的编译条件;
- 对于*.c 与 *.cpp 文件,gcc 分别当作c 与 cpp 文件编译,g++ 统一当作cpp 文件编译;
- g++ 在编译过程中预设了更多的宏定义(__cplusplus ...);
- 对于g++ 编译过程,会将程序中extern ”C"{ } 范围内的代码,按照C 规则进行编译;保证C++ 能够正常调用C 编写的库文件;
- g++ 编译文件时候,会自动链接标准库STL, gcc 则不会(但是处理cpp时候仍然可以链接c库,如<stdio.h>, 也可以用标准库<cstdio>);
+++++++++++++++++++gcc使用一些细节+++++++++++++++++++
静态库 、 动态库的调用:
在正式说静态库 与 动态库之前,在C/C++ 中全局变量 与 函数 有声明 与 定义 ,extern关键字在其中又很重要作用;简单点来说,对于函数来讲,默认就是extern(写不写都可以) , 可以被外部引用;对于全局变量来讲,如果要外部引用,声明中必须要有extern;此外extern 在编译中还有作为链接指示符的作用(例如extern "C"),指示该部分内容是由其他程序语言j(C)编写,编译规则会有不同;
在程序编写中,有些函数/模块是不用重复制造,那么就可以把这部分的程序通过库文件提供给大家使用,这里就分为静态库 与 动态库;
因为在windows与 linux 不同平台下,库文件也是相互不兼容的,所以下面操作都是针对linux 下GNU gcc 编译器;
库通常以前缀“ lib”命名。对于所有C标准库都是如此。链接时,对库的命令行引用将不包含库前缀或后缀;
静态库 的格式一般是以.a 结尾(windows 中是.lib 文件),使用ar 命令可以生成静态库文件;
- 静态库在编译过程中,链接器将代码编译进程序中(如下图),没有对于外界库文件依赖;
- 但是编译后文件较大,如果有多个程序调用同一段代码,会在内存中存在多个代码副本;
- 另外如果库文件有更新以后,需要重新编译程序才能实现更新;
基本操作如下:
#具体命令 ar rcs libstatic1.a module1.o module2.o sub_module.o #生成静态库文件,将这几个o 文件打包到libstatic1.a 中 g++ main.cpp -L./ -lstatic1 #使用静态库方法, #命令细节: # ar 是 Linux 的一个备份压缩命令,它可以将多个文件打包成一个备份文件(也叫归档文件),也可以从备份文件中提取成员文件。ar 命令最常见的用法是将目标文件打包为静态链接库。 #参数 r 用来替换库中已有的目标文件,或者加入新的目标文件。 #参数 c 表示创建一个库。不管库否存在,都将创建。 #参数 s 用来创建目标文件索引,这在创建较大的库时能提高速度
#g++ #-L : 指明静态库的搜索路径; 静态库链接时候搜索路径顺序是:1.-L 指定路径,2.gcc 环境变量LIBRARY_PATH; 3默认路径为 /lib, /usr/lib, /usr/local/lib #-l: 指明静态库文件的名字;(这里不需要lib 前缀, .a 后缀),gcc 使用时候会自动加上前后缀
动态库 一般以.so 结尾(windows 中是.lib 与 .dll 文件),可以使用gcc 自带的命令生成动态库;
- 动态库是在代码执行中,才装载进内存;编译程序只是链接关系,编译后的文件较小;(如下图所示,只是链接到特定位置)
- 多个程序可调用内存中同一代码段;
- 如果动态库中一些模块内容变化,但是接口不变,只需要更新动态库,程序不用重新编译;耦合度小;
- 但是一旦动态库更新,必须同时更新动态库 与 程序,而且要保证动态库文件存在,以防发生找不到库文件错误;
#具体命令 g++ -shared -fPIC -o libdynamic1.so module1.cpp module2.cpp subdir/sub_module.cpp g++ main.cpp -L. -ldynamic1 -o result.out -I subdir/ #参数细节 #-shared :生成一个共享对象,然后可以将其与其他对象链接以形成可执行文件。 #-fPIC(或fpic) :生成使用相对地址无关的目标代码(可能会被不同的进程加载到不同的位置上,如果共享对象中的指令使用了绝对地址。那么在共享对象被加载时就必须根据相关模块的加载位置对这个地址做调整,也就是修改这些地址,让它在对应进程中能正确访问,那么就不能实现多进程共享一份物理内存(无法动态共享)) #-Ldir :在动态库的搜索路径中增加dir目录;默认搜索顺序:1.-L 指定路径;2.LD_LIBRARY_PATH 指定路径;3.配置文件/etc/ld.so.conf 中指定位置;4.默认路径: /lib, /usr/lib #-lname :链接静态库(libname.a)或动态库(libname.so)的库文件 #-I : 指明源文件中inlcude 搜索路径;
但是当我们试图运行程序时候,就会发现报错了。这里就涉及到gcc 中一些环境变量知识;
因为程序运行时候,是利用系统动态载入器(dynamic linker/loader 可以参考man ld.so 细节) 来定位动态库的位置的;
默认搜索顺序:1. 在二进制文件中存在的DT_RPATH ;2.LD_LIBRARY_PATH 指定路径;3.配置文件/etc/ld.so.conf 中指定位置;4.默认路径: /lib, /usr/lib
所以根据上面的顺序,可以通过下面方法指定动态库的绝对位置:
- 1.更改LD_LIBRARY_PATH 中内容,具体操作如下;(多个路径的话用: 分隔;查看所有环境变量可以使用export;export 命令要提供绝对路径)
关于GCC 的环境变量,还有其他很多信息,下面是网上收录的一些信息;
1 -Idir 2 3 表示增加dir为头文件的搜索路径,这个路径优先于系统的默认路径,所以用自己指定的头文件来替代系统默认的头文件。但是不要用这个选项来指定路径不要包括供应商提供的系统头文件(这个情况可以用-isystem),如果有多个-I选项,则路径的搜索先后顺序是从左到右的。,即在前面的路径会被选搜索。另外,如果dir以=号开头即如–I=dir,而其中的=号为被sysroot前缀替换。 4 5 如果一个标准系统包含的目录或者用-isystem选项指定的目录同时用了-I选项,则-I选项会被忽略。那个目录仍然会被搜索,只是和没有指定-I选项时一样。这是为了确保GCC程序能过够修复系统头文件的bug和非故意的改变include_next指令的顺序。如果你确实需要改变系统路径的搜索顺序,那你可以用”-nostdinc” 和/或者“-isystem”选项。 6 7 -nostdinc 8 9 该选项指示不要搜索头文件的标准路径(即默认路径),而只搜索-I选项指定的路径和当前路径。 10 11 -isysroot dir 12 13 该选项和—sysroot选项差不多,但只用于搜索头文件。 14 15 --sysroot=dir 16 17 用dir作为头文件和库文件的逻辑根目录,例如,正常情况下,如果编译器在/usr/include搜索头文件,在/usr/lib下搜索库文件,它将用dir/usr/include和dir/usr/lib替代原来的相应路径。如果你同时使用了-isysroot选项,则—sysroot会应用于库文件的搜索而-isysroot会用于搜索头文件。 18 19 20 21 -system dir 22 23 该选项用于搜索头文件,但该选项指定的目录估在-I选项指定的目录后搜索而在系统默认路径前搜索。如果dir前有“=”号,则该“=”号会被sysroot前缀替换。 24 25 26 27 -iquotedir 28 29 增加用于搜索#include “file”类型的头文件的路径,并且不搜索#include<file>类型的头文件,要不就和-I选项一样了。 30 31 32 33 -Ldir 34 35 增加-l选项指定的库文件的搜索路径,即编译器会到dir路径下搜索-l指定的库文件。 36 37 -Bprefix 38 39 这个选项指定GCC到哪去查找自己的可执行文件、库文件、头文件和数据文件。编译器驱动程序运行一个或多个子程序如ccp,cc1,as,ld。当编译器需要运行某个子程序时,它将prefix作业子程序的前缀(如prefix/as)。为了正确运行每个子程序,编译器驱动程序首先-B选项指定的前缀,如果那个可文件(如prefix/as)没有查找到或者没有指定-B选项,则编译器驱动程序将尝试使用标准前缀:/usr/lib/gcc/ 和/usr/local/lib/gcc/。如果用这两个前缀也没查找到,则使用没有修改过的程序名(如as)在PATH环境变量指定的路径下查找。 40 41 编译器会核查-B选项提供的前缀(目录),必要情况下编译器会在最后加一个分隔符。如-B/usr/bin 则最后的前缀会为:/usr/bin/,编译器在最后增加了分隔符。 42 43 -B选项指定的前缀同样对链接器查找库文件有效,因为编译器会将该选项转换为-L选项。另外-B选项同样也对预处理器查找头文件时有效,因为编译器会将该选项转换为预处理器中的-isystem选项,在这种情况下,编译器会在prefix最后加上“include”即prefix/include。 44 45 如果需要,运行时库文件“libgcc.a”同样会通过-B选项指定的前缀来查找,如果没找到,则接着会尝试用上面提到的那两个标准前缀查找,如果还没有找到,则会忽略掉对libgcc.a的链接。 46 47 另外一种指定prefix前缀的方式是通过环境变量GCC_EXEC_PREFIX来指定,不过会在-B选项指定的前缀之后搜索。即先尝试用-B选项指定的前缀再尝试使用GCC_EXEC_PREFIX指定的前缀,再用标准前缀。其实前面提到的两个标准前缀应该是在安装编译器是指它的!!所以每个系统可能还不一样。 48 49 -specs=file 50 51 为了复盖GCC传递给cc1、cc1plus、as、ld等子程序的默认开关选项,编译器会在读取标准的“specs”文件后再处理file文件。如果在-specs中指定了多个文件,则这些文件会被按照从左到右的顺序来处理。 52 53 54 55 -print-file-name=library 56 57 打印出链接时将用到的library的绝对路径,这样可以看看链接的是不是确实是自己想要的库文件。 58 59 -print-libgcc-file-name 60 61 打印出所使用的libgcc.a文件的路径,这个特别是在使用了-nostdlib或者-nodefaultlibs选项时有用。等价于-print-file-name=libgcc.a 62 63 64 65 -print-search-dirs 66 67 打印出GCC安装路径、程序列表和GCC标准的默认搜索路径。 68 69 70 71 -print-sysroot-headers-suffix 72 73 打印出头文件的根目录。 74 75 76 77 -dumpmachine 78 79 打印出GCC编译的程序的目标机器。 80 81 82 83 -dumpspecs 84 85 打印出GCC的内建specs文件,这个文件在编译GCC时会用到。 86 87 88 89 GCC_EXEC_PREFIX 90 91 该环境变量用于指定编译器子程序(如as,cc1等)的前缀,编译器不会给该前缀增加“/”,但你可以自己增加。如果GCC_EXEC_PREFIX变量没有设置的话,GCC会尝试gcc所在路径为前缀。如果用指定的前缀没有找到子程序的话,GCC将在默认位置查找子程序。 92 93 GCC_EXEC_PREFIX的默认值为:‘prefix/lib/gcc/’这里的prefix为安装gcc时指定的prefix,一般与configure配置时指定的一样。 94 95 用-B指定的前缀会在该环境变量前缀前先搜索。该环境变量指定的前缀同样也用于查找链接所需的文件如“crt0.o”. 96 97 除此之外,GCC还用这个指定的前缀来搜索头文件,对于一个标准头文件的目录来说一般都以“/usr/local/lib/gcc”(更精确定的是用GCC_INCLUDE_DIR来指定),GCC首先会尝试用GCC_EXEC_PREFIX指定的前缀去替换掉“/usr/local/lib/gcc”查找头文件,然后才在标准的前缀目录下搜索头文件。 98 99 100 101 COMPILER_PATH 102 103 GCC在该环境变量指定的目录下查找子程序,但是会在GCC_EXEC_PREFIX指定的目录后搜索。 104 105 106 107 LIBRARY_PATH 108 109 GCC如果在GCC_EXEC_PREFIX没有找到链接文件的话,然后会在该环境变量指定的目录下查找链接文件,另外还在-L指定的目录后搜索。 110 111 112 113 CPATH 114 115 C_INCLUDE_PATH 116 117 CPLUS_INCLUDE_PATH 118 119 这些变量指定的目录的分隔符为分号(windows),或者为冒号(linux)。CPATH指定的路径的功能与-I项指定的一样,只是会在-I选项指定的目录后查找,另外对任何语言都适用。其它的两个环境变量是对特定语言的,同-isystem选项一样,但会在-system选项指定的目录后搜索。在这些环境变量中,一个空的元素是指示编译器在当前目录下查找。 120 121 添加动态库命令 122 123 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/my_lib_dir 124 125 添加.pc库 126 127 export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/my_pkgconfig_dir 128 129 130 总结: 131 132 用于搜索头文件的选项: 133 134 1. -Idir -isystem dir -Bprefix -sysroot dir --sysroot=dir -iquote dir 135 136 2. GCC_EXEC_PREFIX CPATH C_INCLUDE_PATH、CPLUS_INCLUDE_PATH 137 138 139 140 用于搜索库文件的选项: 141 142 1. -Ldir -Bprefix 143 144 2. GCC_EXEC_PREFIX 145 146 3. LIBRARY_PATH 147 148 149 150 用于搜索GCC子程序的选项: 151 152 1. -Bprefix 153 154 2. GCC_EXEC_PREFIX 155 156 3. COMPILER_PATH 157 158 4. GCC安装时的路径(可以通过gcc –print-search-dirs查询) 159 160 5. PATH 161 162 编译选项与环境变量部分信息
- 2. 我们可以通过更新 /etc/ld.so.conf ,将自定义路径加入其中, 然后运行ldconfig 来更新 /etc/ld.so.cache 文件,这些操作需要su 权限;具体操作如下;
- a. 增加一个新文件包含自定义路径:
-
- b. 将自定义路径加入默认文件中;
另外,如果使用中同时存在相同名字的动态库 与 静态库,那么默认会使用动态库,当然也可以指定使用静态库;
g++ -o result.out main.cpp -L. -Wl,-Bstatic -ldynamic1 -I./subdir/ #通过--Bstaic/dynamic 来选择哪种库
还有动态库,一般会把版本信息保留在名字中,在使用时候通过link 来实现使用中动态库文件名的不变;
工具(cmd)用来查看库文件信息:
- ldd :使用ldd工具,查看可执行程序依赖那些动态库或着动态库依赖于那些动态库:
- nm:查看静态库和动态库中有那些函数名,nm工具可以打印出库中的涉及到的所有符号,这里的库既可以是静态的也可以是动态的。
nm列出的符号有很多, 常见的有三种:
- 一种是在库中被调用,但并没有在库中定义(表明需要其他库支持),用U表示;
- 一种是在库中定义的函数,用T表示,这是最常见的;
- 另外一种是所 谓的"弱态"符号,它们虽然在库中被定义,但是可能被其他库中的同名符号覆盖,用W表示。
=============一些注意事项==============
- gcc 命令使用过程中,有些选项是多字符的,所以每个选项都是独立使用,不要和其他选项合并在一起,这与linux 系统中一些cmd 用法不同,(manual 里面举例了-dv 是不同于 -d -v 的意思的);如果同一选项多次使用,会按照顺序去选这该选项对应的内容;