使用GCC创建和使用静态库和动态库,并且举例实现&&GCC的常用命令、编译过程和了解EFE文件格式

一、使用GCC创建静态库和动态库

1.编译程序hello.h,hello.c和main.c

首先创建目录,然后访问目录在目录里面创建hello.h头文件,声明函数hello(const char *name)

nano + hello.h

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

然后创建目录,然后访问目录在目录里面创建hello.c头文件,声明函数hello(const char *name)

nano + hello.c

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

最后创建目录, 然后访问目录在目录里面创建main.c头文件,调用hello(const char *name)函数

nano + main.c

#include "hello.h" 
void main()
{ 
   hello("eveyone!");
}
2.然后将hello.c文件编译生成hello.o文件

使用gcc -c命令将hello.c文件编译生成hello.o文件

gcc -c hello.c

查看文件:

已经有hello.o文件生成

3.用hello.o文件创建静态库
ar -crv libhello_c.a hello.o

查看是否已经生成静态库

4.然后在程序中使用静态库

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

方法一:

gcc main.c libhello_c.a -o hello_1

查看是否生成可执行文件:

并且执行可执行文件

成功输出 Hello everyone!

方法二:

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

查看是否生成可执行文件:

并且执行可执行文件

方法三:

gcc -c main.c 
gcc -o hello_3 main.o libhello_c.a

查看是否生成可执行文件:

并且执行可执行文件

以上使用是用静态库生成可执行文件编译成功

5.创建动态库

在系统提示符下输入以下命令得到静态库文件libhello_c.so

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

然后查看是否生成动态库:

6.在程序中使用动态库

方法一:

gcc main.c libhello_c -o hello_4

然后会出现这样的问题

No such file or directory

此时我们可以在终端:

cp libhello_c.so /usr/lib

执行该文件:

这样重新执行也可以输出Hello everyone!

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

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

rm -f /usr/lib/libhello_c.so

然后在终端输入

gcc -o hell0_5 -L . -lhello_c

查看生成的hello_5文件,并且执行

 从这里我们可以看出系统调用的是动态库文件,因为没有找到库文件,这个时候就要将动态库的文件复制到usr/bin目录下即可。

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

1.sub1.c、sub2.c、sub.h、main.c程序的编写

sub1.c

#include "sub.h"
float x2x(int a,int b)
{
 float c=0;
  c=a*b;
return c;
}

sub2.c

#include "sub.h"
float x2y(int a,int b)
{
float c=0;
c=a/b;
return c;
}

 sub.h

#ifndef SUB_H
#define SUB_H
float x2x(int a,int b);
float x2y(int a,int b);
#endif

 main.c

#include<stdio.h>
#include"sub.h"
void main()
{
int a, b ;
printf("Please input the value of a:");
scanf("%d", &a);
printf("Please input the value of b:");
scanf("%d", &b);
printf("a*b=%.2f\n", x2x(a,b));
printf("a/b=%.2f\n", x2y(a,b));
}

程序编写完成过后在终端输入代码生成对应的.o文件

gcc -c sub1.c sub2.c

2.生成静态库

ar crv libsub.a sub1.o sub2.o
gcc -c main main.c libsub.a

然后查看可执行文件,并且执行该文件:

3.生成动态库

gcc -shared -fpic -o libsub.so sub1.o sub2.o

查看是否生成libsub.so

已经成功生成

然后在终端输入

gcc main.c libsub.so -o main2

然后执行main2,成功编译生成

 三.静态库和动态库的大小比较

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

四.GCC的常用命令及编译过程

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

                      主函数代码:

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

1.简单编译

gcc main.c -o main

2.实际编译

①预编译

命令: 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 编 译 器 指 令 , 后 续 编 译 过 程 需 要 使 用 它 们 。

gcc -E main.c -o main.i

nano +main.i

main.i 代码:

# 1 "main.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 31 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "<command-line>" 2
# 1 "main.c"

# 1 "/usr/include/stdio.h" 1 3 4
# 27 "/usr/include/stdio.h" 3 4
# 1 "/usr/include/x86_64-linux-gnu/bits/libc-header-start.h" 1 3 4
# 33 "/usr/include/x86_64-linux-gnu/bits/libc-header-start.h" 3 4
# 1 "/usr/include/features.h" 1 3 4
# 424 "/usr/include/features.h" 3 4
# 1 "/usr/include/x86_64-linux-gnu/sys/cdefs.h" 1 3 4
# 427 "/usr/include/x86_64-linux-gnu/sys/cdefs.h" 3 4
# 1 "/usr/include/x86_64-linux-gnu/bits/wordsize.h" 1 3 4
# 428 "/usr/include/x86_64-linux-gnu/sys/cdefs.h" 2 3 4
# 1 "/usr/include/x86_64-linux-gnu/bits/long-double.h" 1 3 4
# 429 "/usr/include/x86_64-linux-gnu/sys/cdefs.h" 2 3 4
# 425 "/usr/include/features.h" 2 3 4
# 448 "/usr/include/features.h" 3 4
# 1 "/usr/include/x86_64-linux-gnu/gnu/stubs.h" 1 3 4
# 10 "/usr/include/x86_64-linux-gnu/gnu/stubs.h" 3 4
# 1 "/usr/include/x86_64-linux-gnu/gnu/stubs-64.h" 1 3 4
# 11 "/usr/include/x86_64-linux-gnu/gnu/stubs.h" 2 3 4
# 449 "/usr/include/features.h" 2 3 4
# 34 "/usr/include/x86_64-linux-gnu/bits/libc-header-start.h" 2 3 4
# 28 "/usr/include/stdio.h" 2 3 4





# 1 "/usr/lib/gcc/x86_64-linux-gnu/7/include/stddef.h" 1 3 4
# 216 "/usr/lib/gcc/x86_64-linux-gnu/7/include/stddef.h" 3 4

# 216 "/usr/lib/gcc/x86_64-linux-gnu/7/include/stddef.h" 3 4
typedef long unsigned int size_t;
# 34 "/usr/include/stdio.h" 2 3 4

# 1 "/usr/include/x86_64-linux-gnu/bits/types.h" 1 3 4
# 27 "/usr/include/x86_64-linux-gnu/bits/types.h" 3 4
# 1 "/usr/include/x86_64-linux-gnu/bits/wordsize.h" 1 3 4
# 28 "/usr/include/x86_64-linux-gnu/bits/types.h" 2 3 4


typedef unsigned char __u_char;
typedef unsigned short int __u_short;
typedef unsigned int __u_int;

②编译为汇编代码

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

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

gcc -S main.i -o main.s

nano + main.s

main.s 代码:

   .file   "main.c"
        .text
        .section        .rodata
.LC0:
        .string "Hello World!"
        .text
        .globl  main
        .type   main, @function
main:
.LFB0:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        leaq    .LC0(%rip), %rdi
        call    puts@PLT
        nop
        popq    %rbp
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE0:
        .size   main, .-main
        .ident  "GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0"
        .section        .note.GNU-stack,"",@progbits

③汇编

gcc -c main.s -o main.o 

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

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

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

gcc mian.o -o main

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

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

最后执行可执行文件main即可:

五、gcc的合作伙伴

这里只介绍部分工具

    1.addr21line
    帮助调试器在调试过程中定位对应的源代码。
   2. ar
    用于创建静态链接库。
   3. ld
    用于链接。
   4. as
    用于汇编。
    5.ldd
    查看执行文件所用到的链接库。
    6.size
    查看执行文件中各部分的大小。
    7.readelf
    查看ELF各个部分的内容。
    8.objdump
    进行反汇编。
    举例:
    采用动态链接库

   采用静态链接库

通过上面两种方式的比较,可以看到采用动态链接库会时执行文件小很多

六.   ELF介绍

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

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

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

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

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

ELF 头:

节头:

Key to Flags:

 

该程序没有程序头表:

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

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


.............

七:总结

1.

gcc编译得到.o文件 gcc -c hello.c
创建静态库 ar -crv libmyhello.a hello.o
创建动态库 gcc -shared -fPIC -o libmyhello.so hello.o
使用库生成可执行文件 gcc -o hello main.c -L. -lmyhello
执行可执行文件 ./hello

2.当静态库和动态库同时存在的时候,  执行可执行文件,会报一个错误,程序会优先使用动态库。

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值