目录
1. 编辑生成例子程序 hello.h、hello.c和main.c
1. gcc编译过程和命令(以简单输出“Hello World!”程序为例)
2. ELF(Executable and Linkable Format)文件介绍
一、gcc生成动态库和静态库
1. 编辑生成例子程序 hello.h、hello.c和main.c
先创建一个作业目录,在里面编写这些程序。
创建 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
3. 由.o文件创建静态库
静态库文件名的命名规范是以 lib 为前缀,紧接着跟静态库名,扩展名为.a。例如:我们将创建的静态库名为 myhello,则静态库文件名就是 libmyhello.a。在创建和使用静态库时,需要注意这点。创建静态库用 ar 命令。在系统提示符下键入以下命令将创建静态库文libmyhello.a。
ar -crv lib+(库名)+.a .o文件
4. 在程序中使用静态库
静态库制作完了,如何使用它内部的函数呢?只需要在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后在用 gcc 命令生成目标文件时指明静态库名,gcc 将会从静态库中将公用函数连接到目标文件中。注意,gcc 会在静态库名前加上前缀 lib,然后追加扩展名.a 得到的静态库文件名来查找静态库文件。
在程序 3:main.c 中,我们包含了静态库的头文件 hello.h,然后在主程序 main 中直接调用公用函数 hello。下面先生成目标程序 hello,然后运行 hello 程序看看结果如何。
法一:
gcc -o hello main.c -L . -lmyhello
查看生成的 hello 可执行文件,并且运行:
输出了Hello everyone!
法二:
gcc main.c libmyhello.a -o hello1
查看生成的hello1可执行文件,并且运行
输出Hello everyone!
法三:
先生成main.o : gcc -c main.c
再生成可执行文件: gcc -o hello3 main.o libmyhello.a
查看生成的 hello3 可执行文件,运行:
同样也生成了 hello3 可执行文件,且结果输出相同
5. 创建动态库
动态库文件名命名规范和静态库文件名命名规范类似,也是在动态库名增加前缀 lib,但其文件扩展名为.so。例如:我们将创建的动态库名为 myhello,则动态库文件名就是 libmyh
ello.so。用 gcc 来创建动态库。
在系统提示符下键入以下命令得到动态库文件 libmyhello.so
gcc -shared -fpic -o libmyhello.so hello.o
可见成功生成了 libmyhello.so 动态库
6. 在程序中使用动态库
在程序中使用动态库和使用静态库完全一样,也是在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后在用 gcc 命令生成目标文件时指明动态库名进行编译。我们先运行 gcc 命令生成目标文件,再运行它看看结果。
这里也可以用静态库法一的方法: gcc -o hello main.c -L . -lmyhello
但是这里会出现混淆,因为动态库名和静态库名都是myhello,因此这里可以这样写:
读错:没有找到动态库文件
程序在运行时,会在/usr/lib 和/lib 等目录中查找需要的动态库文件。若找到,则载入动态库,否则将提示类似上述错误而终止程序运行。我们将文件 libmyhello.so 复制到目录/usr/lib 中,在试试。
在终端输入: cp libmyhello.so /usr/lib
重新运行 hello4 可执行文件
输出了相同结果
7. 当动态库和静态库同名时,会优先选择动态库
首先删除在 /usr/lib 下面的动态库文件 libmyhello.so :
rm -f /usr/lib/libmyhello.so
然后 在终端中输入: gcc -o hello5 -L. -lmyhello
查看生成的 hello5 可执行文件并运行:
可见,系统是调用的动态库文件,因为没有找到库文件
这时候需要将动态库文件复制到 /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));
}
2. 创建和使用静态库和动态库,查看文件大小
静态库的创建:
将两个源文件编译为.o文件 gcc -c x2x.c x2y.c
将这两个.o文件创建静态库文件: ar -crv libcalculate.a x2x.o x2y.o
主函数与静态库链接,生成可执行文件 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
动态库的创建和使用:
创建动态库: gcc -shared -fpic -o libcalculate.so x2x.o x2y.o
与主函数链接生成 main2 可执行文件:gcc main.c libcalculate.so -o main2
将libcalculate.so 文件拷贝到 /usr/lib目录下,再执行main2可执行文件:
与调用时静态库输出相同的结果
查看可执行文件大小:
可见,调用静态库可执行文件main1的大小位 8416Byte
调用动态库可执行文件main2的大小位 8360Byte
使用静态库的可执行文件大小更大, 这是因为:
静 态 库 和 动 态 库 的 不 同 点 在 于 代 码 被 载 入 的 时 刻 不 同 。 静 态 库 的 代 码 在 编译 过 程 中 已 经 被 载 入 可 执 行 程 序 , 因 此 体 积 较 大 。 共 享 库 的 代 码 是 在 可 执行 程 序 运 行 时 才 载 入 内 存 的 , 在 编 译 过 程 中 仅 简 单 的 引 用 , 因 此 代 码 体 积较 小 。 在 Linux 系 统 中 , 可 以 用 ldd 命 令 查 看 一 个 可 执 行 程 序 依 赖 的 共 享库 。
三、gcc的常用命令及编译过程
1. gcc编译过程和命令(以简单输出“Hello World!”程序为例)
主函数代码:
#include <stdio.h>
void main()
{
printf("%s\n","Hello World!");
}
gcc将一个c语言程序编译为可执行程序一般分为四步:
预处理、编译、汇编、链接。
计算机能够执行的是机器语言,即一长串的二进制数字:1001000111111……
但是这么多二进制数字很难记忆,因此把一些二进制数字封装起来,方便记忆,
就形成了汇编语言,汇编语言是最接近底层的语言。
高级语言最终也需要通过一系列的操作最后形成可执行的二进制数字,这样计算机才能
识别和运行,其过程如下:
一个高级语言程序,通过预处理,形成源代码,通过编译器,形成汇编文件,再通过汇编器,形成二进制文件,最终通过连接器,生成可执行文件,其大体过程就是如此,下面是四个步骤的详解。
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 编 译 器 指 令 , 后 续 编 译 过 程 需 要 使 用 它 们 。
预处理主函数:
main.i 代码段:
B. 编译(Compilation)
命令: gcc -S main.i 或者 gcc -S main.i -o main.s 生成main.s 汇编文件
编译的过程就是对 预 处 理 完 的 文 件 进 行 一 系 列 的 词 法 分 析 ,语 法 分 析 ,语 义 分 析 及优化后生成相应的汇编代码。
编译main.i源文件:
main.s 代码段:
C. 汇编(Assembly)
命令: gcc -c main.s -o main.o 或者用as工具 : as -c main.s -o main.o
汇 编 过 程 调 用 对 汇 编 代 码 进 行 处 理 ,生 成 处 理 器 能 识 别 的 指 令 ,保 存 在 后 缀 为 .o的 目 标 文 件 中 。由 于 每 一 个 汇 编 语 句 几 乎 都 对 应 一 条 处 理 器 指 令 ,因 此 ,汇 编 相对 于 编 译 过 程 比 较 简 单 ,通 过 调 用 Binutils 中 的 汇 编 器 as 根 据 汇 编 指 令 和 处 理器指令的对照表一一翻译即可。当 程 序 由 多 个 源 代 码 文 件 构 成 时 , 每 个 文 件 都 要 先 完 成 汇 编 工 作 , 生 成 .o 目 标文 件 后 ,才 能 进 入 下 一 步 的 链 接 工 作 。注 意 :目 标 文 件 已 经 是 最 终 程 序 的 某 一 部分了,但是在链接之前还不能执行。
用as将main.s编译为目标文件:
main.o目标文件为 ELF(Executable and Linkable Format)可执行和可链接的文件
D. 链接(Linking)
命令:gcc main.o main
链接也分为静态链接和动态链接,其要点如下:
(1) 静 态 链 接 是 指 在 编 译 阶 段 直 接 把 静 态 库 加 入 到 可 执 行 文 件 中 去 ,这 样 可 执 行文 件 会 比 较 大 。链 接 器 将 函 数 的 代 码 从 其 所 在 地( 不 同 的 目 标 文 件 或 静 态 链接 库 中 )拷 贝 到 最 终 的 可 执 行 程 序 中 。为 创 建 可 执 行 文 件 ,链 接 器 必 须 要 完成 的 主 要 任 务 是 :符 号 解 析( 把 目 标 文 件 中 符 号 的 定 义 和 引 用 联 系 起 来 )和重定位(把符号定义和内存地址对应起来然后修改所有对符号的引用)。
(2) 动 态 链 接 则 是 指 链 接 阶 段 仅 仅 只 加 入 一 些 描 述 信 息 ,而 程 序 执 行 时 再 从 系 统中把相应动态库加载到内存中去。
链接,最后生成可执行文件:
执行main可执行文件:
输出了 “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 描述了节的基本信息
使用 readelf -a main.o 查看各部分的内容:
ELF header:
Section header table:
该程序没有程序头表:
查看具体功能和含义可参考:ELF文件详解_.elf文件怎么查看-CSDN博客
也可以通过反汇编的操作进行分析:
gcc -o main.o -g main.c
objdump -S main.o
四、总结
1. 在gcc编译环境下,使用动态库文件比使用静态库文件更加节省空间
2. gcc一般分为四步骤:预处理、编译、汇编和链接
3. 汇编是将汇编语言转为文件格式为ELF的目标文件,ELF一般分为四个部分:ELF 头,程序头、段和段头
参考链接: ELF文件详解_.elf文件怎么查看-CSDN博客
Linux GCC 编译详解_源世界yu的博客-CSDN博客
源码下载:
交流群 :456948834