GCC背后的故事及GCC与库的爱情
一、用GCC生成.a静态库和.so动态库
1️⃣、创建一个实验文件夹,并在该文件夹下创建3个子程序hello.h、hello.c、main.c
程序hello.h内容如下:
#ifndefHELLO_H
#defineHELLO_H
voidhello(constchar*name);
#endif//HELLO_H
程序hello.c内容如下:
#include<stdio.h>
voidhello(constchar*name)
{
printf("Hello%s!\n",name);
}
程序main.c内容如下:
#include"hello.h"
intmain()
{
hello("everyone");
return0;
}
2️⃣、将hello.c编译成.o文件
无论静态库,还是动态库,都是由.o文件创建的,所以我们必须将源程序hello.c通过gcc先编译成.o文件
gcc -c hello.c
通过以上命令得到hello.o文件,让我们来看看是否生成了
3️⃣、由.o文件创建静态库
静态库文件名的命名规范是以lib为前缀,紧接着跟静态库名,扩展名为.a
接下来,我们创建静态库时就将用到ar命令
ar -crv libmyhello.a hello.o
通过以上命令得到静态库文件libmyhello.a,让我们继续看看是否生成
4️⃣、在程序中使用静态库
我们包含了静态库的头文件 hello.h,然后在主程序 main 中直接调用 公用函数 hello。下面先生成目标程序 hello,然后运行 hello 程序看看结果如何。
gcc -c main.c
gcc -o hello main.o libmyhello.a
用如上命令,先生成main.o,再生成可执行文件的方法,完成程序连接静态库的操作(在下面的动态库连接时也可以用同样的方法)
让我们来对静态库进行一个小测试,看看是否公用函数hello是否真的连接到了目标文件hello中了,让我们来删除掉静态库文件试试
程序仍然正常运行,看样子静态库中的公用函数已经连接到目标文件中了。
5️⃣、由.o文件创建动态库文件
动态库与静态库的文件名命名格式类似,同样是在动态库名增加前缀lib,但是其扩展名为.so
让我们用如下命令进行操作生成动态库文件libmyhello.so
gcc -shared -fPIC -o libmyhello.so hello.o
6️⃣、在程序中使用动态库
我们用同样的方式来使用动态库
操作后报错,原因是找不到动态库文件libmyhello.so。程序在运行时,会在/usr/lib和/lib等目录中查找需要的动态库文件。那么我们将libmyhello.so复制到目录/usr/lib中,再试试。
同样的,我们继续来做一个测试,删除.c和.h外的所有文件。
恢复成如图所示。再次创建静态库文件和动态库文件
然后我们运行gcc命令来使用函数库myhello生成目标文件hello,并运行hello。
和上面相同的错误,那么一样是默认连接到/usr/lib目录中,只需要将动态库文件复制到该目录下即可
当静态库和动态库同名时,gcc命令将优先使用动态库
二、静态库与动态库生成执行文件大小比较
1️⃣、创建一个work文件夹,来保存这次实验,在该文件夹中分别创建子文件sub1.c、sub2.c、main.c,用gcc 命令分别将三个子文件编译为.o的目标文件
sub1.c中内容如下:
#include<stdio.h>
flaot x2x(int a,int b)
{
float c;
c=a*b;
return c;
}
sub2.c中内容如下:
#include<stdio.h>
float y2y(int a,int b)
{
float c;
c=a+b;
return c;
}
main.c中内容如下:
#include<stdio.h>
int main()
{
int a=3;int b=4;
float c,d;
c=x2x(a,b);
d=y2y(a,b);
printf("%f","%f",c,d);
return 0;
}
(1)完成三个基本文件的编译后,将生成使用sub1.o和sub2.o文件创建静态库
(2)在程序中使用静态库
(3)创建动态库
(4)在程序中使用动态库
(5)比较两个可执行文件的大小
三、gcc编译器是如何编译的
(1)、创建一个test文件夹,在其中加入一个hello.c程序
hello.c内容如下:
#include<stdio.h>
int main()
{
printf("hello!\n");
return 0;
}
(2)程序的编译过程包含以下几个过程
①预编译(将源文件hello.c文件预处理生成hello.i)
②编译(将预处理生成的hello.i文件编译生成汇编程序hello.s)
③汇编(将编译生成hello.s文件汇编生成目标文件hello.o)
④链接(分为静态和动态链接,生成可执行文件)
(3)ELF文件的分析
一个典型的ELF文件包含下面几个段
①.text:已编译程序的指令代码段
②.rodata:ro代表read only,即只读数据(譬如常熟const)
③.data:已初始化的C程序全局变量和静态局部变量
④.bss:未初始化的C程序全局变量和静态局部变量
⑤.debug:调试符号表,调试器用此段的信息帮助调试
readelf -S hello
(3)反汇编ELF
可以使用objdump -D 对其进行反汇编
一、在Ubuntu下安装nasm
在Ubuntu18.04.5下打开搜狐浏览器,在其中https://www.nasm.us/pub/nasm/releasebuilds/2.14rc16/nasm-2.14rc16.tar.gz搜索以上链接,进行下载
随后,进行解压
cd 下载
tar zxvf nasm-2.14rc16.tar.gz
安装
cd nasm-2.14rc16/
./configure
make
sudo make install
查看安装是否成功
如图是安装好后的NASM版本
二、编译
编译汇编hello.asm文件,并于C代码的编译生成的程序大小进行比较
hello.asm内容如下:
; hello.asm
section .data ; 数据段声明
msg db "Hello, world!", 0xA ; 要输出的字符串
len equ $ - msg ; 字串长度
section .text ; 代码段声明
global _start ; 指定入口函数
_start: ; 在屏幕上显示一个字符串
mov edx, len ; 参数三:字符串长度
mov ecx, msg ; 参数二:要显示的字符串
mov ebx, 1 ; 参数一:文件描述符(stdout)
mov eax, 4 ; 系统调用号(sys_write)
int 0x80 ; 调用内核功能
; 退出程序
mov ebx, 0 ; 参数一:退出代码
mov eax, 1 ; 系统调用号(sys_exit)
int 0x80 ; 调用内核功能
编译
nasm -f elf64 hello.asm
链接
ld -s -o hello hello.o
大小比较
四、了解实际程序是如何借助第三方库函数完成代码设计
(一)、以游客身份体验一下即将绝迹的远古时代的BBS
在win10下,打开控制面板——>程序——>启用或关闭Windows功能,启动"telnet client" 和"适用于Linux的Windows子系统"如图
接下来我们以游客的身份进入
(二)、Linux环境下C语言编译实现弹球游戏
①在Ubuntu中用sudo apt-get install libncurses5-dev 安装curses库。
②新建一个文件夹,放人实现弹球游戏代码完成编译。内容参考curses库实现的弹球游戏
编译该.c文件
![在这里插入图片描述](https://img-blog.csdnimg.cn/20201017054531635.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lhbmdydW41OTk=,size_16,color_FFFFFF,t_70#pic_center
./game开始这个游戏,效果如下:
五、总结
过此次实验了解如何用 gcc 生成静态库(.a)和动态库(.so),更加了解了程序在编译的整个过程,有了崭新的体验,在第三方库函数方面有了更多的理解