GCC编译步骤、动态库和静态库的创建和使用、ELF库简介及查看方法

目录

 

一、gcc生成动态库和静态库

           1.  编辑生成例子程序 hello.h、hello.c和main.c

           2. 将 hello.c 编译为.o文件

           3.  由.o文件创建静态库

           4.  在程序中使用静态库

           5.  创建动态库

           6.  在程序中使用动态库

           7.  当动态库和静态库同名时,会优先选择动态库

二、举例使用动态库和静态库

          1. 程序功能和编写

           2.  创建和使用静态库和动态库,查看文件大小

三、gcc的常用命令及编译过程

          1.  gcc编译过程和命令(以简单输出“Hello World!”程序为例)

          A . 预处理(Preprocessing)

          B.  编译(Compilation)

          C.   汇编(Assembly)

          D.   链接(Linking)

              2.   ELF(Executable and Linkable Format)文件介绍

四、总结


 

一、gcc生成动态库和静态库

           1.  编辑生成例子程序 hello.h、hello.c和main.c

                      先创建一个作业目录,在里面编写这些程序。

                     5fdfaf4c7f51427abe8cbaba6edf1700.png          

                      创建 hello.h 头文件,声明函数 hello(const char * name):


#ifndef HELLO_H
#define HELLO_H
void hello(const char *name);
#endif 

                    创建 hello.c 源文件,定义 函数hello(const char *name):


#include <stdio.h>
void hello(const char *name)
{ 
 printf("Hello %s\n",name);
}

                   创建main.c源文件 ,在主函数中调用 hello(const char *name)函数:

#include "hello.h" 
void main()
{ 
   hello("eveyone!");
}

       2. 将 hello.c 编译为.o文件

                 用gcc-c 命令将 hello.c 文件生成 hello.o 文件,并用ls命令查看是否生成:

                  gcc -c hello.c

9afac6681d2549fb89a2afa8fb1cb788.png

       3.  由.o文件创建静态库

                 静态库文件名的命名规范是以 lib 为前缀,紧接着跟静态库名,扩展名为.a。例如:我们将创建的静态库名为 myhello,则静态库文件名就是 libmyhello.a。在创建和使用静态库时,需要注意这点。创建静态库用 ar 命令。在系统提示符下键入以下命令将创建静态库文libmyhello.a。

                 ar -crv lib+(库名)+.a  .o文件

becf9a77b8894a8490bd1f3638f3eb15.png

         4.  在程序中使用静态库

                   静态库制作完了,如何使用它内部的函数呢?只需要在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后在用 gcc 命令生成目标文件时指明静态库名,gcc 将会从静态库中将公用函数连接到目标文件中。注意,gcc 会在静态库名前加上前缀 lib,然后追加扩展名.a 得到的静态库文件名来查找静态库文件。

                   在程序 3:main.c 中,我们包含了静态库的头文件 hello.h,然后在主程序 main 中直接调用公用函数 hello。下面先生成目标程序 hello,然后运行 hello 程序看看结果如何。

        法一:

gcc -o hello main.c -L . -lmyhello

                  查看生成的 hello 可执行文件,并且运行:
                3ae31f218a2d425a830018f3029e7fe4.png

                   输出了Hello everyone! 

        法二:

gcc main.c libmyhello.a -o hello1

                   查看生成的hello1可执行文件,并且运行

195f6c5d14fc4e54b04d50c1c680fe14.png

                   输出Hello everyone!

        法三:

                   先生成main.o  : gcc  -c  main.c

                   再生成可执行文件: gcc -o hello3 main.o  libmyhello.a

                   查看生成的 hello3 可执行文件,运行:

0601d57050694ba2904db8358d3fe10e.png

                  同样也生成了 hello3 可执行文件,且结果输出相同

        5.  创建动态库

                   动态库文件名命名规范和静态库文件名命名规范类似,也是在动态库名增加前缀 lib,但其文件扩展名为.so。例如:我们将创建的动态库名为 myhello,则动态库文件名就是 libmyh
ello.so。用 gcc 来创建动态库。
                  在系统提示符下键入以下命令得到动态库文件 libmyhello.so

                   gcc -shared -fpic -o libmyhello.so hello.o

7c304a7d8ee34ee8b112a17099d2b788.png

                  可见成功生成了 libmyhello.so 动态库

        6.  在程序中使用动态库

                    在程序中使用动态库和使用静态库完全一样,也是在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后在用 gcc 命令生成目标文件时指明动态库名进行编译。我们先运行 gcc 命令生成目标文件,再运行它看看结果。 

                     这里也可以用静态库法一的方法: gcc -o hello  main.c  -L . -lmyhello

                     但是这里会出现混淆,因为动态库名和静态库名都是myhello,因此这里可以这样写:

                                         88dec47ecd744d6d9a2c737ee771fef5.png

                  读错:没有找到动态库文件

                 程序在运行时,会在/usr/lib 和/lib 等目录中查找需要的动态库文件。若找到,则载入动态库,否则将提示类似上述错误而终止程序运行。我们将文件 libmyhello.so 复制到目录/usr/lib 中,在试试。

                  在终端输入: cp libmyhello.so  /usr/lib

                  重新运行 hello4 可执行文件

7b0e31046d3f4864b3d5e6854d020c35.png

                   输出了相同结果

        7.  当动态库和静态库同名时,会优先选择动态库

                    首先删除在 /usr/lib 下面的动态库文件 libmyhello.so  :

                    rm -f   /usr/lib/libmyhello.so

                    然后 在终端中输入: gcc  -o  hello5 -L.  -lmyhello

                   查看生成的 hello5 可执行文件并运行:ffadbcd9153a43c3816c48a22b38aa17.png

                    可见,系统是调用的动态库文件,因为没有找到库文件

                   这时候需要将动态库文件复制到 /usr/bin目录下

   

二、举例使用动态库和静态库

       1. 程序功能和编写

         程序功能:x2x.c函数中完成两个数减法的程序,x2y.c完成两个数加法的程序,

                             主函数main.c调用二者

        x2x.c函数编写:


#include <stdio.h>
float sub(float a,float b)
     {
        return a-b;
     }

       x2x.h函数编写:

     

#ifndef X2X_H
#define X2X_H
float sub(float a,float b);
#endif

       x2y函数编写:


#include <stdio.h>
float add(float a,float b)
     {
        return a+b;
     }

      x2y.h函数编写:

#ifndef X2Y_H
#define X2Y_H
float  add(float a,float b);
#endif

      main 函数编写:

#include <stdio.h>
#include "x2x.h"
#include "x2y.h"

void main()
{
   float a=3.00,b=2.00;
   printf("%f\n",sub(a,b));
   printf("%f\n",add(a,b));
}

b81072fa78504d138970ae6851548211.png

      2.  创建和使用静态库和动态库,查看文件大小

                 静态库的创建:

                将两个源文件编译为.o文件       gcc -c x2x.c x2y.c

                将这两个.o文件创建静态库文件: ar -crv  libcalculate.a  x2x.o x2y.o               

9336cfd8286d4c4fbcfb6c887a9e9ab7.png               主函数与静态库链接,生成可执行文件 main1 : gcc -o main1 mian1.c   -L. -lcalculate

                或者:                                  gcc  main.c libcalculate.a -o  main1

                或者:       gcc -c main.c              gcc  -o   main1  main.o  libcalculate.a

                df358d5d9a85402e9681088053e1b4e0.png

                动态库的创建和使用:

                 创建动态库: gcc -shared -fpic -o  libcalculate.so x2x.o x2y.o

                 与主函数链接生成 main2 可执行文件:gcc main.c libcalculate.so -o main2

                                49c7e7ab7c504d1a8efe777629749626.png

             将libcalculate.so 文件拷贝到 /usr/lib目录下,再执行main2可执行文件:

   cda665b8b246499188a46c166c8c0c78.png

             与调用时静态库输出相同的结果

             查看可执行文件大小:

             4ccb9def81b640629fa51d54b7b9dc16.png

            可见,调用静态库可执行文件main1的大小位 8416Byte

                         调用动态库可执行文件main2的大小位 8360Byte

            使用静态库的可执行文件大小更大, 这是因为

            静 态 库 和 动 态 库 的 不 同 点 在 于 代 码 被 载 入 的 时 刻 不 同 。 静 态 库 的 代 码 在 编译 过 程 中 已 经 被 载 入 可 执 行 程 序 , 因 此 体 积 较 大 。 共 享 库 的 代 码 是 在 可 执行 程 序 运 行 时 才 载 入 内 存 的 , 在 编 译 过 程 中 仅 简 单 的 引 用 , 因 此 代 码 体 积较 小 。 在 Linux 系 统 中 , 可 以 用 ldd 命 令 查 看 一 个 可 执 行 程 序 依 赖 的 共 享库 。

三、gcc的常用命令及编译过程

          1.  gcc编译过程和命令(以简单输出“Hello World!”程序为例)

                      主函数代码:

                      


#include <stdio.h>
void main()
{
    printf("%s\n","Hello World!");
}                             


                      gcc将一个c语言程序编译为可执行程序一般分为四步:

                      预处理、编译、汇编、链接。

                     计算机能够执行的是机器语言,即一长串的二进制数字:1001000111111……

                     但是这么多二进制数字很难记忆,因此把一些二进制数字封装起来,方便记忆,

                     就形成了汇编语言,汇编语言是最接近底层的语言。

                     高级语言最终也需要通过一系列的操作最后形成可执行的二进制数字,这样计算机才能

                     识别和运行,其过程如下:

           

08ee8b3c54524226a7de2785365bcb8b.png

237591cf7bc544838b6163a8e5efed55.png

                    一个高级语言程序,通过预处理,形成源代码,通过编译器,形成汇编文件,再通过汇编器,形成二进制文件,最终通过连接器,生成可执行文件,其大体过程就是如此,下面是四个步骤的详解。

       A . 预处理(Preprocessing)

                 命令: gcc -E + main.c (输出main.i)    或者      gcc -E  main.c -o main.i

                 预处理过程:

               (1) 将 所 有 的 #define 删 除 , 并 且 展 开 所 有 的 宏 定 义 , 并 且 处 理 所 有 的 条 件 预 编
                       译 指 令 , 比 如 #if #ifdef #elif #else #endif 等
               (2) 处 理 #include 预 编 译 指 令 , 将 被 包 含 的 文 件 插 入 到 该 预 编 译 指 令 的 位 置 。
               (3) 删 除 所 有 注 释 “ //” 和 “ /* */” 。
               (4) 添 加 行 号 和 文 件 标 识 , 以 便 编 译 时 产 生 调 试 用 的 行 号 及 编 译 错 误 警 告 行 号
               (5) 保 留 所 有 的 #pragma 编 译 器 指 令 , 后 续 编 译 过 程 需 要 使 用 它 们 。

                预处理主函数:9494445c653341339b7742932ce2d1ff.png

                main.i 代码段:             

a06a92d145dc4ced898a64c78cc783f0.png

          B.  编译(Compilation)

                   命令:    gcc -S main.i  或者   gcc -S main.i  -o main.s    生成main.s 汇编文件

                   编译的过程就是对 预 处 理 完 的 文 件 进 行 一 系 列 的 词 法 分 析 ,语 法 分 析 ,语 义 分 析 及优化后生成相应的汇编代码。

                 编译main.i源文件:

3921e53d132f449d9d9f7ec53852d40a.png

               main.s 代码段:

            

d0fb3b744f6a4b98b3834f88367a92d7.png

       C.   汇编(Assembly)

                        命令:  gcc -c main.s -o main.o  或者用as工具 :  as -c main.s  -o main.o

           汇 编 过 程 调 用 对 汇 编 代 码 进 行 处 理 ,生 成 处 理 器 能 识 别 的 指 令 ,保 存 在 后 缀 为 .o的 目 标 文 件 中 。由 于 每 一 个 汇 编 语 句 几 乎 都 对 应 一 条 处 理 器 指 令 ,因 此 ,汇 编 相对 于 编 译 过 程 比 较 简 单 ,通 过 调 用 Binutils 中 的 汇 编 器 as 根 据 汇 编 指 令 和 处 理器指令的对照表一一翻译即可。当 程 序 由 多 个 源 代 码 文 件 构 成 时 , 每 个 文 件 都 要 先 完 成 汇 编 工 作 , 生 成 .o 目 标文 件 后 ,才 能 进 入 下 一 步 的 链 接 工 作 。注 意 :目 标 文 件 已 经 是 最 终 程 序 的 某 一 部分了,但是在链接之前还不能执行。

                    用as将main.s编译为目标文件:

2ba87141245742eda58dc2e7380b7165.png

                     main.o目标文件为 ELF(Executable and Linkable Format)可执行和可链接的文件

         D.   链接(Linking)

                   命令:gcc  main.o main

                 链接也分为静态链接和动态链接,其要点如下:

              (1) 静 态 链 接 是 指 在 编 译 阶 段 直 接 把 静 态 库 加 入 到 可 执 行 文 件 中 去 ,这 样 可 执 行文 件 会 比 较 大 。链 接 器 将 函 数 的 代 码 从 其 所 在 地( 不 同 的 目 标 文 件 或 静 态 链接 库 中 )拷 贝 到 最 终 的 可 执 行 程 序 中 。为 创 建 可 执 行 文 件 ,链 接 器 必 须 要 完成 的 主 要 任 务 是 :符 号 解 析( 把 目 标 文 件 中 符 号 的 定 义 和 引 用 联 系 起 来 )和重定位(把符号定义和内存地址对应起来然后修改所有对符号的引用)。
              (2) 动 态 链 接 则 是 指 链 接 阶 段 仅 仅 只 加 入 一 些 描 述 信 息 ,而 程 序 执 行 时 再 从 系 统中把相应动态库加载到内存中去。      

                链接,最后生成可执行文件:         fe5bd8aa3c4e419ebd93b6250d9dbc09.png

               执行main可执行文件:

6ef9117b7df54cec8ab2c1035f838caf.png

               输出了 “Hello World!”

  •    2.   ELF(Executable and Linkable Format)文件介绍

               通过上面的操作,发现一个高级语言程序,通过预处理、编译、汇编之后生成了main.o

               目标文件的ELF格式文件,这到底是一种怎样的文件格式呢? ELF是一种用于二进制文件、可执行文件、目标代码、共享库和核心转储格式文件的文件格式。

               一个ELF文件一般由四个部分组成:

               ELF header、Program header table 、Section、Section header table

              ELF header 里面存放着很多整体分布的信息,如文件类型、版本信息等;Program header table 描述的是一个段在文件中的位置、大小以及它被放进 内存后所在的位置和大小;Section 将文件分成一个个节区,每个节区都有其对应的功能,如.text 用于存放机器码;Section header table 描述了节的基本信息

 

1c5e9fc9c86b41eabd82458821def3a0.png

 

f513453e550c453e99b4f2d66436c746.png

 使用 readelf -a main.o 查看各部分的内容:

 ELF header:

98516a6e75844d98bfff715c166b1383.png

Section header table:

2bf4a9f09bd44287b2851b9d6cf63566.png

 该程序没有程序头表:

4c32806f1d634a02b30a2860327137db.png

查看具体功能和含义可参考:ELF文件详解_.elf文件怎么查看-CSDN博客

也可以通过反汇编的操作进行分析:

gcc -o main.o -g  main.c
objdump -S main.o

e2d651c48e614cb997ab05d37dac4fb7.png

四、总结

            1.  在gcc编译环境下,使用动态库文件比使用静态库文件更加节省空间

            2.  gcc一般分为四步骤:预处理、编译、汇编和链接

            3.  汇编是将汇编语言转为文件格式为ELF的目标文件,ELF一般分为四个部分:ELF 头,程序头、段和段头

参考链接:      ELF文件详解_.elf文件怎么查看-CSDN博客

                       Linux GCC 编译详解_源世界yu的博客-CSDN博客

 

 

  • 7
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值