编译器背后的故事

一、可执行程序是如何被组装

一个源程序到一个可执行程序的过程:预编译、编译、汇编、链接。
1.预编译:主要处理源代码文件中的以“#”开头的预编译指令。
2.编译:把预编译之后生成的xxx.i或xxx.ii文件,进行一系列词法分析、语法分析、语义分析及优化后,生成相应的汇编代码文件。
3.汇编:汇编过程调用对汇编代码进行处理,生成处理器能识别的指令,保存在后缀为.o的目标文件中。
4.链接:链接也分为静态链接和动态链接

二、如何创建静态库和动态库

在创建函数库前,我们先来准备举例用的源程序,并将函数库的源程序编译成.o 文件

1、编辑生成例子程序 hello.h 、hello.c 和 和 main.c。

(1)创建一个作业目录并移到此目录
在这里插入图片描述
(2)编辑hello.h 、hello.c 和 和 main.c
在这里插入图片描述

hello.h
#ifndef HELLO_H
#define HELLO_H
void hello(const char *name);
#endif //HELLO_H
hello.c
#include <stdio.h>
void hello(const char *name)
{
printf("Hello %s!\n", name);
}
main.c
#include "hello.h"
int main()
{
hello("everyone");
return 0;
}

2、将 hello.c 编译成.o 文件 文件 。

无论静态库,还是动态库,都是由.o 文件创建的。因此,我们必须将源程序 hello.c 通过 g
cc 先编译成.o 文件。在系统提示符下键入以下命令得到 hello.o 文件。
在这里插入图片描述
我们运行 ls 命令看看是否生成了 hello.o 文件。
在这里插入图片描述

在 ls 命令结果中,我们看到了 hello.o 文件,本步操作完成。

3、由.o 文件创建静态库

静态库文件名的命名规范是以 lib 为前缀,紧接着跟静态库名,扩展名为.a。例如:我们将
创建的静态库名为 myhello,则静态库文件名就是 libmyhello.a。在创建和使用静态库时,
需要注意这点。创建静态库用 ar 命令。在系统提示符下键入以下命令将创建静态库文件
libmyhello.a。
在这里插入图片描述
ls 命令结果中有 libmyhello.a。

4、在程序中使用静态库 。

在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后在用 gcc 命令生成目标文件时指明静态库名,gcc 将会从静态库中将公用函数连接到目标文件中。注意,gcc 会在静态库名前加上前缀 lib,然后追加扩展名.a 得到的静态库文件名来查找静态库文件。

1、使用 gcc main.c libmyhello.a -o hello生成目标文件时指明静态库名,并使用./hello运行

在这里插入图片描述

5、在程序中使用动态库

在程序中使用动态库和使用静态库完全一样,也是在使用到这些公用函数的源程序中包含
这些公用函数的原型声明,然后在用 gcc 命令生成目标文件时指明动态库名进行编译
(1)、由.o 文件创建动态库文件并使用ls查询是否生成
在这里插入图片描述
创建动态库文件成功
(2)在程序中使用动态库

在这里插入图片描述
./hello 会提示出错,因为虽然连接时用的是当前目录的动态库,但是运行时,是到/usr/lib 中找库文件的,将文件 libmyhello.so 复制到目录/usr/lib 中就 OK 了
在这里插入图片描述

三、静态库.a 与.so 库文件的生成与使用

1、先创建一个作业目录

在这里插入图片描述

2、用 vim文本编辑器编辑生成所需要的四个文件 A1.c 、 A2.c、 A.h、test.c

在这里插入图片描述



A1.c:
#include <stdio.h>
void print1(int arg){
printf("A1 print arg:%d\n",arg);
}
A2.c:
#include <stdio.h>
void print2(char *arg){
printf("A2 printf arg:%s\n", arg);
}
A.h
#ifndef A_H
#define A_H
void print1(int);
void print2(char *);
#endif
test.c:
#include <stdlib.h>
#include "A.h"
int main(){
print1(1);
print2("test");
exit(0);
}

3、静态库.a 文件的生成与使用。

(1)生成目标文件(xxx.o)
在这里插入图片描述
(2)生成静态库.a 文件
在这里插入图片描述
(3)使用.a 库文件,创建可执行程序并运行
在这里插入图片描述

4、动态库.so 文件的生成与使用

(1)生成目标文件(xxx.o) (此处生成.o 文件必须添加"-fpic"(小模式,代码少),否则在生成.so
文件时会出错
在这里插入图片描述

(2)生成共享库.so 文件
在这里插入图片描述
(3)使用.so 库文件,创建可执行程序并运行
在这里插入图片描述
由于 linux 自身系统设定的相应的设置的原因,即其只在 /lib and /usr/lib 下搜索对应的.so 文件,故需将对应 so 文件拷贝到对应路径。
在这里插入图片描述
成功

5、将x2x、x2y目标文件用 ar工具生成1个 .a 静态库文件, 然后用 gcc将 main函数的目标文件与此静态库文件进行链接,生成最终的可执行程序

(1)使用vim编辑三个.c文件
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
(2)生成.o文件在使用ar生成静态库文件
在这里插入图片描述
(3)使用gcc链接,生成可执行文件
在这里插入图片描述
成功
(4)比较生成文件大小
在这里插入图片描述
在这里插入图片描述

四、Linux GCC 常用命令

这个程序,一步到位的编译指令是:

gcc test.c -o test

实质上,上述编译过程是分为四个阶段进行的,即预处理(也称预编译,Preprocessing)、编译(Compilation)、汇编 (Assembly)和连接(Linking)。

1 预处理(Preprocessing)、编译(Compilation)、汇编 (Assembly)和连接(Linking)。

1.1 预处理

gcc -E test.c -o test.i 或 gcc -E test.c

1.2 编译
预处理之后,可直接对生成的 test.i 文件编译,生成汇编代码:

gcc -S test.i -o test.s

gcc 的-S 选项,表示在程序编译期间,在生成汇编代码后,停止,-o 输出汇编代码文件。
1.3汇编
生成的汇编代码文件 test.s,gas 汇编器负责将其编译为目标文件,如下:

gcc -c test.s -o test.o

1.4 连接
gcc 连接器是 gas 提供的,负责将程序的目标文件与所需的所有附加的目标文件连接起来,最终生成可执行文件。附加的目标文件包括静态连接库和动态连接库。
对于上一小节中生成的 test.o,将其与C标准输入输出库进行连接,最终生成程序 test

gcc test.o -o test

1.5 运行
执行./test, 让它说 HelloWorld 吧!
2 总结
GCC 不是一个人在战斗,GCC 背后其实有一堆战友。
(1) addr2line:用来将程序地址转换成其所对应的程序源文件及所对应的代码行,也可以得到所对应的函数。该工具将帮助调试器在调试的过程中定位对应的源代码位置。
(2) as:主要用于汇编
(3) ld:主要用于链接
(4) ar:主要用于创建静态库。
(5) ldd:可以用于查看一个可执行程序依赖的共享库。
(6) objcopy:将一种对象文件翻译成另一种格式,譬如将.bin 转换成.elf、或者将.elf 转换成.bin 等。
(7) objdump:主要的作用是反汇编。
(8) readelf:显示有关 ELF 文件的信息
(9) size:列出可执行文件每个部分的尺寸和总尺寸,代码段、数据段、总大小等

五、as汇编编译器

1、安装nasm

在这里插入图片描述
出现了进程阻塞现象。输入以下两行命令,把进程锁的缓存文件删除即可。

sudo rm /var/cache/apt/archives/lock
sudo rm /var/lib/dpkg/lock

2、创建hello.asm文件输入代码

在这里插入图片描述

在这里插入图片描述

3、从hello.asm 生成 目标文件hello.o,

在这里插入图片描述

4、从.o文件生成可执行文件运行

在这里插入图片描述

5、与C代码的编译生成的程序大小进行对比。

在这里插入图片描述
在这里插入图片描述
可以发现使用nasm生成的可执行文件占用内存小得多。

六、借助第三方库函数完成代码设计

1、了解Linux 系统中终端程序最常用的光标库(curses)的主要函数功能

1、安装curses
使用命令:

sudo apt-get install libncurses5-dev

在这里插入图片描述

2 、头文件(比如curses.h)和库文件的安装目录
curses函数库的头文件和库文件就被分别安装在/usr/include/和/usr/lib/下

3、主要函数功能
initscr(): initscr()是一般curses程式必须先呼叫的函数,一但这个函数被呼叫之后, 系统将根据终端机的形态并启动curses模式。

endwin(): curses通常以呼叫endwin()来结束程式.endwin()可用来关闭curses模式。

echo():用来控制从键盘输入字元时是否将字元显示在终端机上.系统预设是开启的.

move(y,x): 将游标移动至 x,y 的位置.

getyx(win,y,x): 得到目前游标的位置.

2、体验一下即将绝迹的远古时代的 BBS

1、在 win10 系统中,“控制面板”–>“程序”—>“启用或关闭Windows功能”,启用 “telnet client” 和"适用于Linux的Windows子系统"(后面会使用)。
在这里插入图片描述
2、打开一个cmd命令行窗口,命令行输入 telnet bbs.newsmth.net即可使用

在这里插入图片描述

3、c语言实现贪吃蛇游戏

参考Linux 环境下C语言编译实现贪吃蛇游戏中的代码,使用编译命令:gcc mysnake1.0.c -lcurses -o mysnake1.0 体会curses库如何被链接和使用
在这里插入图片描述

在这里插入图片描述

七、总结

本次学习带我们了解了gcc的工作流程,预编译、编译、汇编和链接,可执行文件是如何组装的。在我们编写自己的代码时,使用第三方库的文件或许会更节省时间,提高效率。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值