一、使用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 头,程序头、段和段头