编译器背后的故事
一、可执行程序是如何被组装
一个源程序到一个可执行程序的过程:预编译、编译、汇编、链接。
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的工作流程,预编译、编译、汇编和链接,可执行文件是如何组装的。在我们编写自己的代码时,使用第三方库的文件或许会更节省时间,提高效率。