文章目录
一、gcc生成.a静态库与.so动态库
静态库:在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库。
动态库:库在程 序编译时并不会被连接到目标代码中,而是在程序运行是才被载入,因此在程序运行时还需 要动态库存在。
1.创建子程序
创建 test1 文件夹,并在该文件夹中创建三个子程序 hello.h、hello.c 和 main.c
mkdir test1 //创建test1文件夹
cd test1 //进入该文件
nano hello.h //编辑hello.h
nano hello.c //编辑hello.c
nano main.c //编辑main.c
- hello.h代码:
#ifndef HELLO_H
#define HELLO_H
void hello(const char *name);
#endif
- hello.c代码:
#include <stdio.h>
#include "hello.h"
void hello(const char *name)
{
printf("Hello %s!\n", name);
}
- main.c代码
#include "hello.h"
int main()
{
hello("schoolmate");
return 0;
}
2.将 hello.c 编译成 .o文件
gcc -c hello.c //编译成 .o文件,无论静态库,还是动态库,都是由.o 文件创建的,因此需先编译成 .o文件
ls //查看
3.由 .o文件创建静态库,并在程序中使用
- .o文件创建静态库
ar -crv libmyhello.a hello.o //生成静态库
ls //查看
- 使用静态库
gcc -o hello main.c -L. -lmyhello //方法一
gcc main.c libmyhello.a -o hello //方法一
gcc -o main.c //方法三,先生成 main.o
gcc -o hello main.o libmyhello.a
- 执行程序及结果
- 删除 libmyhello静态库,再次执行 hello 程序(看程序运行时,是否需要该静态库)
rm libmyhello.a //删除libmyhello.a
./hello //运行hello程序
同样能输出说明静态库在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库。
4.由 .o文件创建动态库,并在程序中使用
- .o文件创建动态库
动态库文件名的命名规范是以 lib 为前缀,紧接着跟静态库名,扩展名为 .so。如:libmyhello.so
gcc -shared -fPIC -o libmyhello.so hello.o //生成动态库
ls //查看
- 使用动态库
gcc -o hello main.c -L. -lmyhello //方法一
gcc main.c libmyhello.so -o hello //方法二
由于当静态库和动态库同名时,gcc 命令将优先使用动态库,默认去连/usr/lib 和/lib 等目录中的动态库
所以将文件 libmyhello.so 移动到目录/usr/lib 中
sudo mv libmyhello.so /usr/lib //移动文件
./hello
结果:
二、动态库和静态库生成可执行文件大小的对比
1.创建子程序
- 创建一个 test2文件夹,并在该文件夹中分别创建子程序 sub1.h、sub1.c、sub2.h、sub2.c、main.c
mkdir test2
cd test2
nano sub1.h
nano sub1.c
nano sub2.h
nano sub2.c
nano main.c
- sub1.h代码
#ifndef SUB1_H
#define SUB1_H
float x2x(int a, int b);
#endif //SUB1_H
- sub1.c代码
#include "sub1.h"
float x2x(int a, int b){
return a + b; //相加
}
- sub2.h代码
#ifndef SUB2_H
#define SUB2_H
float x2y(int a, int b);
#endif //SUB2_H
- sub2.c代码
#include"sub2.h"
float x2y(int a, int b){
return a * b; //相乘
}
- main.c代码
#include<stdio.h>
#include"sub1.h"
#include"sub2.h"
int main(){
int a = 2, b = 3;
printf("%d + %d = %f\n", a, b, x2x(a, b));
printf("%d × %d = %f\n", a, b, x2y(a, b));
return 0;
}
2.用静态库文件进行链接,生成可执行文件
- 将 sub1.c、sub2.c 编译成 .o文件
gcc -c sub1.c sub2.c
ls
- .o文件创建静态库
ar -crv libsub1.a sub1.o
ar -crv libsub2.a sub2.o
ls
- 在程序中使用静态库
gcc main.c libsub1.a libsub2.a -o main1
./main1
3.用动态库文件进行链接,生成可执行文件
- .o文件创建动态库
gcc -shared -fPIC -o libsub1.so sub1.o
gcc -shared -fPIC -o libsub2.so sub2.o
ls
- 在程序中使用动态库
gcc main.c libsub1.so libsub2.so -o main2//将文件 libsub1.so、libsub2.so 移动到目录/usr/lib 中
sudo mv libsub1.so /usr/lib
sudo mv libsub2.so /usr/lib
./main2
- 两个可执行文件大小的比较并重新由静态库链接生成一个可执行文件
gcc -static main.c libsub1.a libsub2.a -o main1 //重新由静态库生成
size main1
ldd main1
size main2
ldd main2
三、gcc的编译过程
1.创建子程序
- 创建一个 test3文件夹,并在该文件夹中创建一个 hello.c 程序
mkdir test3
cd test3
nano hello.c
-
hello.c代码
#include <stdio.h>
int main(void)
{
printf(“Hello World! \n”);
return 0;
} -
程序的编译过程
-
预编译(将源文件 hello.c 文件预处理生成 hello.i)
gcc -E hello.c -o hello.i
- 编译(将预处理生成的 hello.i 文件编译生成汇编程序 hello.s)
gcc -S hello.i -o hello.s
- 汇编(将编译生成的 hello.s 文件汇编生成目标文件 hello.o)
gcc -c hello.s -o hello.o //用gcc进行汇编
as -c hello.s -o hello.o //用as进行汇编
- 链接(分为静态链接和动态链接,生成可执行文件)
gcc hello.c -o hello //动态链接
gcc -static hello.c -o hello //静态链接
- 用 size 查看文件大小,ldd链接了那些动态库
3.分析ELF 文件
- 一个典型的 ELF 文件包含下面几个段:
(1) .text:已编译程序的指令代码段
(2) .rodata:ro 代表 read only,即只读数据(譬如常数 const)
(3) .data:已初始化的 C 程序全局变量和静态局部变量
(4) .bss:未初始化的 C 程序全局变量和静态局部变量
(5) .debug:调试符号表,调试器用此段的信息帮助调试
readelf -S hello //查看各个section(段)的信息
- 反汇编 ELF
gcc -o hello -g hello.c
objdump -S hello
4.nasm编译
- 安装nasm
sudo apt install nasm
- 编译汇编 hello.asm文件
mkdir test4
nano hello.asm
nasm -f elf64 hello.asm
ld -s -o hello hello.o
./hello
- 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 ; 调用内核功能
- 汇编与C代码的编译生成的可执行程序大小对比
四、了解实际程序是如何借助第三方库函数完成代码设计
1.体验即将绝迹的远古时代的 BBS
以游客身份体验一下即将绝迹的远古时代的 BBS (一个用键盘光标控制的终端程序),在 win10 系统中,“控制面板”→“程序”→“启用或关闭Windows功能”,启用 “telnet client” 和 “适用于Linux的Windows子系统”(后面会使用),然后重启。
- 打开一个 cmd命令行窗口,输入如下命令:
telnet bbs.newsmth.net
2.Linux 环境下C语言编译实现贪吃蛇游戏
- 安装curses库
sudo apt-get install libncurses5-dev
- Linux 环境下C语言编译实现贪吃蛇游戏
mkdir testSnake //新建一个文件夹
cd testSnake //进入该文件
vim mysnake.c
gcc mysnake.c -lcurses -o mysnake //编译链接生成可执行文件
./mysnake
- mysnake.c 的游戏代码内容请参考链接: https://m.linuxidc.com/Linux/2011-08/41375.htm
./mysnake后:
五、总结
此次实验初步学会了使用gcc 生成静态库(.a)和动态库(.so)与使用静/动态库链接生成可执行文件;了解了一个程序的编译过程分为"预编译 —> 编译 —> 汇编 —> 链接" 四个过程。
六、参考链接
链接: GCC编译器背后的故事.