C/C++编译过程

版权声明:xbx https://blog.csdn.net/weiweixiao3/article/details/81879811

这里写图片描述

编译过程 + 链接过程(编译过程是大括号,其余为链接)

编译过程包括编译和汇编。.

这里写图片描述
.c/.cpp 编译预处理–E .i 编译-S .s(汇编代码) 汇编-c .o(目标文件) 链接-o .exe(可执行文件)

1、编译预处理(预处理器)

gcc -E test.c -o test.i // 将test.c预处理输出test.i文件。.i文件里还是c语言

在这个阶段,预处理器在源代码上执行一些文本操作,必不可少的有:头文件展开、宏替换、去掉注释和条件编译等过程。
1. 宏定义替换。
2. 条件编译。如#ifdef,#ifndef,#else,#elif,#endif,等等。一般用在头文件中,防止重复编译。
3. 头文件展开。如#include “FileName”或者#include 等。
4. 特殊符号。

__FINE__    //进行编译的源文件
__LINE__    //文件被编译的当前行号
__ DATE__  //文件被编译的日期
__TIME__   //文件被编译的时间

printf("file:%s\nline:%d\ndate:%s\ntime:%s\n",
        __FILE__, __LINE__, __DATE__, __TIME__);

2、编译(编译器)+ 优化

gcc -S test.i (-o test.s)// 将预处理输出文件test.i编译成test.s文件。.s文件里是汇编语言

经过预编译得到的输出文件中,就只有常量,如数字、字符串、变量的定义,以及C语言的关键字,如main,if,else,for,while,{,},+,-,*,\,等等。

编译就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成汇编代码

优化处理是编译系统中一项比较艰深的技术。
经过优化得到的汇编代码必须经过汇编程序的汇编转换成相应的机器指令,方可能被机器执行。

3、汇编

gcc -c test.s (-o test.o) // 将编译输出文件test.s汇编成test.o目标文件。
gcc -c test.i -o test.o // 将预处理输出文件test.i编译成test.o目标文件

汇编过程实际上指把汇编代码翻译成目标机器指令的过程。
每一个C语言源程序,都将最终经过这一处理而得到相应的目标文件。目标文件中所存放的也就是与源程序等效的目标的机器语言代码。
目标文件由段组成。通常一个目标文件中至少有两个段:
1. 代码段:该段中所包含的主要是程序的指令。该段一般是可读和可执行的,但一般却不可写。
2. 数据段:主要存放程序中要用到的各种全局变量或静态的数据。一般数据段都是可读,可写,可执行的。

4、链接

gcc test.o -o test // 将.o链接成可执行文件
gcc test.c -o test // 将源文件直接编译汇编链接成成执行文件
汇编得到的目标文件并不能直接被电脑执行,因为一个目标文件里可能引用了其他文件的变量或某个库的函数,所以需要链接,将有关的目标文件彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够被操作系统装入执行的统一整体
链接分两种:静态链接和动态链接

静态链接

在这种链接方式下,函数的代码将从其所在的静态链接库中被拷贝到最终的可执行程序中。这样该程序在被执行时这些代码将被装入到该进程的虚拟地址空间中。
.lib是静态库,编译的时候代码直接插入到可执行程序。用户通过头文件找到库文件中函数实现的代码从而把这段代码链接到用户程序中去。
然而静态链接,简而言之就是将程序运行所需的指令和数据全部装入内存中使得内存可以顺利运行,然而这种方式虽然简单但又粗暴,极大程度的浪费内存空间。针对程序运行时的局部性原理,我们可以将不常用的数据存放在磁盘中,只将不同的程序所需的不同模块装入内存,这样便有效的利用内存空间,即产生动态链接(覆盖装入、页映射)。

动态链接

动态链接的产生,则需要对应的动态库,由于动态库中的各模块可供用户选择使用从而也实现了模块资源共享,因此动态库又称共享库。
在此种方式下,函数的代码被放到称作是动态链接库或共享对象的某个目标文件中。链接程序此时所作的只是在最终的可执行程序中记录下共享对象的名字以及其它少量的登记信息。在此可执行文件被执行时,动态链接库的全部内容将被映射到运行时相应进程的虚地址空间。动态链接程序将根据可执行程序中记录的信息找到相应的函数代码。
.dll是动态库,编译的时候,只是产生一些调用DLL内代码的导入表,真正运行的时候是调用的DLL内的代码。

动态链接库是程序运行时加载的库,当动态链接库正确安装后,所有的程序都可以使用动态库来运行程序。动态链接库是目标文件的集合,目标文件在动态链接库中的组织方式是按照特殊方式形成的。库中函数和变量的地址是相对地址,不是绝对地址,其真实地址在调用动态库的程序加载时形成。
动态链接库的名称有别名(soname), 真名(realname)和链接名(linker name)。别名由一个前缀lib,然后是库的名字,再加上一个后缀“.so”构成。真名是动态链接库真实名称,一般总是在别名的基础加上一个小版本号,发布版本等构成。除此之外,还有一个链接名,即程序链接时使用的库的名字。
在动态链接库安装的时候,总是复制文件到某个目录下,然后用一个软连接生成别名,在库文件进行更新的时候,仅仅更新软链接即可。

二者区别

区别主要在于库中代码被载入的时刻不同。
(1)静态库:在程序编译时期会被连接到目标代码中,由于静态链接只会生成一个可执行代码,因此目标程序运行时不需要再载入。
(2)共享库:在程序编译时期仅简单引用,不会被连接到目标代码中,而是在程序运行时才被载入,因此在程序运行时需要动态库存在。“以时间换取空间”

对于可执行文件中的函数调用,可分别采用动态链接或静态链接的方法。使用动态链接能够使最终的可执行文件比较短小,并且当共享对象被多个进程使用时能节约一些内存,因为在内存中只需要保存一份此共享对象的代码。但并不是使用动态链接就一定比使用静态链接要优越。在某些情况下动态链接可能带来一些性能上损害。

.a代表传统的静态函数库(也称作归档文件:archive)
.so代表共享函数库(共享库就是动态库)

头文件位置一般在/usr/include
库文件位置一般在/usr/lib

二、创建静态库文件 .a:

静态库,还是动态库,都是由.o文件创建的。
1. 创建hello.c, hello.h 和 app.c。
hello.c定义了一个hello函数,app.c包含了hello.h,然后调用了hello函数。
2. 创建.o文件
gcc -c hello.c -> hello.o
3. 由.o文件创建静态库文件.a (库名要在真名前加lib,后加.a)
ar cr libmyhello.a hello.o // 一个或几个.o -> libmyhello.a
4. 使用静态库文件
只需要在使用到这些公用函数的源程序中包含其原型声明,然后在用gcc命令生成目标文件时指明静态库名-L/-l),gcc将会从静态库中将公用函数连接到可执行文件中。
注意,gcc会在静态库名前加上前缀lib,然后追加扩展名.a得到的静态库文件名来查找静态库文件。

在app.c中,我们包含了静态库的头文件hello.h,然后在主程序app中直接调用公用函数hello。下面先生成目标程序app,然后运行hello程序看看结果如何。
gcc -o app app.c -L. -lmyhello //-o指定输出
gcc app.c -o app -L. -lmyhello
gcc app.c -L. -lmyhello -o app //-o file在哪都可以
静态库中的hello函数已经连接到可执行文件app中了,此时删掉静态库,app也能执行。

-L后紧跟库文件所在目录(此处为当前文件夹.),
-l后紧跟库文件的真名
另外-I参数后紧跟头文件目录(有需要的话)
例子:gcc -o hello hello.c -I/home/hello/include -L/home/hello/lib -lworld

三、动态库的生成及使用

静态库有很多缺点,所以有必要使用动态库。

  1. 动态链接库是程序运行时加载的库,当动态链接库正确安装后,所有的程序都可以使用动态库来运行程序。动态链接库是目标文件的集合,目标文件在动态链接库中的组织方式是按照特殊方式形成的。库中函数和变量的地址是相对地址,不是绝对地址,其真实地址在调用动态库的程序加载时形成。
  2. 动态链接库的名称有别名(soname), 真名(realname)和链接名(linker name)。别名由一个前缀lib,然后是库的名字,再加上一个后缀“.so”构成。真名是动态链接库真实名称,一般总是在别名的基础加上一个小版本号,发布版本等构成。除此之外,还有一个链接名,即程序链接时使用的库的名字。
  3. 在动态链接库安装的时候,总是复制文件到某个目录下,然后用一个软连接生成别名,在库文件进行更新的时候,仅仅更新软链接即可。

生成和使用动态库:
1. 创建源程序hello.c hello.h
2. 生成目标文件.o,但是此时要加编译器选项-fPIC和链接器选项-shared
gcc -c -fPIC hello.c -o hello.o
3. 生成动态库文件.so
gcc -shared -fPIC -o libmyhello.so hello.o

以上两步也可以一次完成:

gcc -fPIC -shared hello.c -o libmyhello.so
“PIC”命令行标记告诉GCC产生的代码不要包含对函数和变量具体内存位置的引用,这是因为现在还无法知道使用该消息代码的应用程序会将它连接到哪一段内存地址空间。这样编译出的hello.o可以被用于建立共享链接库。建立共享链接库只需要用GCC的”-shared”标记即可。
4. 在程序中使用动态库
在程序中使用动态库和使用静态库完全一样,也是在使用到这些公用函数的源程序中包含其原型声明,然后在用gcc命令生成目标文件时指明动态库名进行编译。
gcc -o app app.c -L. -lmyhello
输出:cannot open shared object file: No such file or directory
需要将库文件libhello.so复制到文件夹/usr/lib下。
./app 即可成功。

四、静态库动态库搜索路径先后顺序

静态库
  1. ld会去找GCC命令中的参数-L
  2. 再找gcc的环境变量LIBRARY_PATH
  3. 再找默认目录 /lib /usr/lib /usr/local/lib
动态库

动态库的搜索路径搜索的先后顺序是:
  1.编译目标代码时指定的动态库搜索路径;
  2.环境变量LD_LIBRARY_PATH指定的动态库搜索路径;
  3.配置文件/etc/ld.so.conf中指定的动态库搜索路径;
  4.默认的动态库搜索路径/lib /usr/lib。

以上4个顺序,当找不到.so文件时对应4种解决方法:
1. gcc编译时加入-Wl,-rpath参数
2. 修改该变量的值为.so所在目录即可: export LD_LIBRARY_PATH=$(pwd)
3. 把.so所在目录增加到该文件也可。
4. 复制.so到默认目录也可。

猜你喜欢

转载自blog.csdn.net/weiweixiao3/article/details/81879811