菜鸟入门3,渐入佳境之走近编译器

在前两篇blog里,我们简单地尝试了如何在Ubuntu环境下编译简单程序和如何在arm开发环境下编写一个stm32简单程序及在proteus仿真一个简单的51程序。
在这篇文章里,我们将一步步靠近编译器,了解关于编译器背后的故事。


前言

本篇通过回答以下问题的方式,一步一步走近,并了解编译器。
1.可执行程序是如何被组装的?

2.gcc编译工具集中各软件的用途?

3.EFF文件格式,汇编语言格式分别是什么?

4.实际程序是如何借助第三方库函数完成代码设计的?


提示:以下是本篇文章正文内容,下面案例可供参考

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

一个源程序到一个可执行程序的过程:预编译、编译、汇编、链接(静态链接和动态链接)。
其中,编译是主要部分,其中又分为六个部分:词法分析、语法分析、语义分析、中间代码生成、目标代码生成和优化。

用gcc生成静态库和动态库

静态库 在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库。
动态库 在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入,因此在程序运行时还需要动态库存在。

1)首先举例说明在Linux中如何创建静态库和动态库,以及使用它们。

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

第 1 步:编辑生成例子程序 hello.h、hello.c 和 main.c。
先创建一个作业目录,保存本次练习的文件。

#mkdir test1 
#cd test1

在这里插入图片描述

然后用 gedit文本编辑器编辑生成所需要的 3 个文件。

hello.c

#ifndef HELLO_H
#define HELLO_H  
void hello(const char *name); 
#endif 

hello.h

#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 文件。
在这里插入图片描述

在 ls 命令结果中,我们看到了 hello.o 文件

下面我们先来看看如何创建静态库,以及使用它。

第 3 步:由.o 文件创建静态库。

静态库文件名的命名规范 :
以 lib 为前缀,紧接着跟静态库名,扩展名为.a。

创建静态库用 ar 命令。

在系统提示符下键入以下命令将创建静态库文件libmyhello.a。

# ar -crv libmyhello.a hello.o

在这里插入图片描述

创建成功

第 4 步:在程序中使用静态库。

静态库制作完了,如何使用它内部的函数呢?只需要在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后在用 gcc 命令生成目标文件时指明静态库名,gcc 将会从静态库中将公用函数连接到目标文件中。

注:gcc 会在静态库名前加上前缀 lib,然后追加扩展名.a 得到的静态库文件名来查找静态库文件。

此处有三种方法
方法一:
#gcc -o hello main.c -L. –lmyhello

方法二:
#gcc main.c libmyhello.a -o hello

方法三:
先生成 main.o:
gcc -c main.c
再生成可执行文件:
gcc -o hello main.o libmyhello.a

我们采用方法三

gcc -c main.c                                \\先生成 main.o
gcc -o hello main.o libmyhello.a             \\再生成可执行文件

在这里插入图片描述

运行文件在这里插入图片描述

我们删除静态库文件试试公用函数 hello 是否真的连接到目标文件 hello 中了。
在这里插入图片描述

程序照常运行,静态库中的公用函数已经连接到目标文件中了。

第 5 步:由.o 文件创建动态库文件。
动态库文件名命名规范:在动态库名增加前缀 lib,文件扩展名为.so。

在系统提示符下键入以下命令得到动态库文件 libmyhello.so。

# gcc -shared -fPIC -o libmyhello.so hello.o (-o 不可少)

在这里插入图片描述
我们照样使用 ls 命令看看动态库文件是否生成。
在这里插入图片描述
(6)第 6 步:在程序中使用动态库;

在程序中使用动态库和使用静态库完全一样,也是在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后在用 gcc 命令生成目标文件时指明动态库名进行编译。我们先运行 gcc 命令生成目标文件,再运行它看看结果。

在这里插入图片描述
运行发现会出错

错误提示是找不到动态库文件 libmyhello.so。程序在运行时, 会在/usr/lib 和/lib 等目录中查找需要的动态库文件。若找到,则载入动态库,否则将提示类似上述错误而终止程序运行。我们将文件 libmyhello.so 复制到目录/usr/lib 中,再试试。

在这里插入图片描述
成功!
说明了动态库在程序运行时是需要的。
ps:当静态库和动态库同名时,gcc 命令将优先使用动态库

(系统是ubuntu16,从hello.c 生成 hello.o,需要将 gcc -c hello.c 换成 gcc -fpic -c hello.c
这样后面才不会出现下面错误:)

在这里插入图片描述

Linux 下静态库.a 与.so 库文件的生成与使用

编辑生成程序 main.c和sub1.c,sub2.c 。
先创建一个作业目录,保存本次练习的文件。

#mkdir test2
#cd test2

在这里插入图片描述

三个程序的代码如下
main.c

#include<stdio.h>
	
#include"sub1.c"

#include"sub2.c"
	
	int main()
	
	{
	
	int a=5;
	
	int b=3;
	
	int c=6;
	
	int d=4;
	
	printf("a x2x b=%f\n",x2x(a,b));
	
	printf("c x2y d=%f\n",x2y(c,d));
	
	return 0;
	
	}

sub1.c

#include<stdio.h>
	
	float x2x(int a,int b)
	
	{
	
	return a+b;
	
	}

sub2.c

#include<stdio.h>
	
	float x2y(int c,int d)
	
	{
	
	return c*d;
	
	}

1.用自己写的程序的生成静态文件并记录大小
把main.c,sub1.c,sub2.c分别编译为3个.o目标文件
在这里插入图片描述

生成静态库,并成功运行
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

用动态库文件进行链接,生成可执行文件

gcc -shared -fPIC -o libsub1.so sub1.o
gcc -shared -fPIC -o libsub2.so sub2.o

使用动态库

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

运行
在这里插入图片描述

main.c 中调用的 stdio.h 是由动态链接的,所以需要重新由静态库链接生成一个可执行文件

gcc -static main.c libsub1.a libsub2.a -o jieguo

查看文件大小并比较,记录如下

在这里插入图片描述

所以静态库生成的可执行文件远大于动态库生成的。

二、gcc编译工具集中各软件的用途?EFF文件格式,汇编语言格式分别是什么?

首先一句话概括:gcc无处不在

gcc的伙伴们有哪些

addr2line 给出一个可执行文件的内部地址,addr2line 使用文件中的调试信息将地址翻泽成源代码文 件名和行号。该程序是 binutils 包的一部分

ar 这是一个程序,可通过从文档中增加、删除和析取文件来维护库文件。通常使用该工具是为了创建和管理连接程序使用的目标库文档。该程序是 binutils 包的一部分

as GNU 汇编器。实际上它是一族汇编器,因为它可以被编泽或能够在各种不同平台上工作。 该程序是 binutils 包的一部分
autoconf 产生的 shell 脚木自动配置源代码包去编泽某个特定版木的 UNIX

c++filt 程序接受被 C++ 编泽程序转换过的名字(不是被重载的),而且将该名字翻泽成初始形式。 该程序是 binutils 包的一部分

f2c 是 Fortran 到C的翻译程序。不是 GCC 的一部分
gcov gprof 使用的配置工具,用来确定程序运行的时候哪一部分耗时最大

gdb GNU 调试器,可用于检查程序运行时的值和行为
GNATS GNU 的调试跟踪系统(GNU Bug Tracking System)。一个跟踪 GCC 和其他 GNU 软件问题的在线系统
gprof 该程序会监督编泽程序的执行过程,并报告程序中各个函数的运行时间,可以根据所提供 的配置文件来优化程序。该程序是 binutils 包的一部分

ld GNU 连接程序。该程序将目标文件的集合组合成可执行程序。该程序是 binutils 包的一部

libtool 一个基本库,支持 make 程序的描述文件使用的简化共享库用法的脚木

make 一个工具程序,它会读 makefile 脚木来确定程序中的哪个部分需要编泽和连接,然后发布 必要的命令。它读出的脚木(叫做 makefile 或 Makefile)定义了文件关系和依赖关系

nlmconv 将可重定位的目标文件转换成 NetWare 可加载模块(NetWare Loadable Module, NLM)。该 程序是 binutils 的一部分

nm 列出目标文件中定义的符号。该程序是 binutils 包的一部分
objcopy 将目标文件从一种二进制格式复制和翻译到另外一种。该程序是 binutils 包的一部分

objdump 显示一个或多个目标文件中保存的多种不同信息。该程序是 binutils 包的一部分

ranlib 创建和添加到 ar 文档的索引。该索引被 Id 使用来定位库中的模块。该程序是 binutils 包的一部分

ratfor Ratfor 预处理程序可由 GCC 激活,但不是标准 GCC 发布版的一部分

readelf 从 ELF 格式的目标文件显示信息。该程序是 binutils 包的一部分

size 列出目标文件中每个部分的名字和尺寸。该程序是 binutils 包的一部分

strings 浏览所有类型的文件,析取出用于显示的字符串。该程序是 binutils 包的一部分

strip 从目标文件或文档库中去掉符号表,以及其他调试所需的信息。该程序是 binutils 包的一部

vcg Ratfor 浏览器从文木文件中读取信息,并以图表形式显示它们。而 vcg 工具并不是 GCC 发布中的一部分,但 -dv 选项可被用来产生 vcg 可以理解的优化数据的格式

windres Window 资源文件编泽程序。该程序是 binutils 包的一部分

参考.
让我们看看,

编译器是怎么工作的

准备工作

先创建一个工作目录 test0,然后用文本编辑器生成一个 C 语言编写的简单 hello.c 程序为示例,其源代码如下所示:

mkdir test3
cd test3
#include <stdio.h>
int main(void)
{
	printf("Hello World! \n");
	return 0;
}

程序的编译过程

在这里插入图片描述
图片出处.

1.预编译(将源文件 hello.c 文件预处理生成 hello.i)

gcc -E hello.c -o hello.i

2.编译(将预处理生成的 hello.i 文件编译生成汇编程序 hello.s)

gcc -S hello.i -o hello.s

3.汇编(将编译生成的 hello.s 文件汇编生成目标文件 hello.o)

用gcc进行汇编

gcc -c hello.s -o hello.o

调用as进行汇编

as -c hello.s -o hello.o

4.链接(分为静态链接和动态链接,生成可执行文件)

gcc hello.c -o hello  // 动态链接
gcc -static hello.c -o hello //静态链接

在这里插入图片描述在这里插入图片描述
用 size 查看文件大小
在这里插入图片描述

用ldd查看文件链接了那些动态库
在这里插入图片描述
说明没有链接动态库

分析ELF文件
一个典型的 ELF 文件包含下面几个段

(1) .text:已编译程序的指令代码段

(2) .rodata:ro 代表 read only,即只读数据(譬如常数 const)

(3) .data:已初始化的 C 程序全局变量和静态局部变量

(4) .bss:未初始化的 C 程序全局变量和静态局部变量

(5) .debug:调试符号表,调试器用此段的信息帮助调试

用readelf -S hello 查看各个section(段)的信息
在这里插入图片描述
反汇编 ELF

使用 objdump -S 将其反汇编并且将其 C 语言源代码混合显示出来:
在这里插入图片描述

安装nasm

在ubuntu中下载安装nasm,对示例汇编代码“hello.asm”编译生成可执行程序,并与上述用C代码的编译生成的可执行程序大小进行对比

按照安装指南安装nasm.用nasm -version查看是否安装成功
在这里插入图片描述
编译汇编 hello.asm文件
把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         ; 调用内核功能

编译
ps:如果用 nasm -f elf hello.asm,生成的是32位的目标文件,后面会遇到问题

nasm -f elf64 hello.asm

链接

ld -s -o hello hello.o

在这里插入图片描述
查看文件大小并与c文件比较
在这里插入图片描述
在这里插入图片描述
汇编编译生成的可执行程序要远小于直接由C代码编译生成的可执行程序。

汇编语句格式

汇编中的语句包括:指令语句,伪指令语句,宏语句。

指令语句:能够产生目标代码,实际执行。

伪操作语句:它不像机器指令那样是在程序运行期间由计算机来执行的,不产生目标代码,它是在汇编程序对源程序汇编期间由汇编程序处理的操作,它们可以完成如数据定义、分配存储区、指示程序结束等功能。(提供控制信息)

宏指令语句:由编程者按照一定的规则来定义的一种较“宏大”的指令,可包括多条指令或伪指令 。(多条指令的组合)

三、实际程序是如何借助第三方库函数完成代码设计的?

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

此处只列举部分
函数基本分为如下几类:

输出到屏幕:

1 int addch(const chtype char_to_add); 2 int addchstr(chtype *const string_to_add); //当前位置添加字符(串) 3 4 int printw(char *format, …); //类似与printf 5 int refresh(void); //刷新物理屏幕 6 int box(WINDOW *win_ptr, chtype vertical, chtype horizontal); //围绕窗口绘制方框 7 int insch(chtype char_to_insert); //插入一个字符(已有字符后移) 8 int insertln(void); //插入空白行 9 int delch(void); 10 int deleteln(void); //删除字符和空白行 11 12 int beep(void); //终端响铃 13 int flash(void); //闪烁

从屏幕读取字符;

1 chtype inch(void); //返回光标位置字符 2 int instr(char *string); // 3 int innstr(char *string, int numbers); //将返回内容写入字符数组中

清除屏幕;

int erase(void); //在屏幕的每个位置写上空白字符 int clear(void); //使用一个终端命令来清除整个屏幕,内部调用了clearok来执行清屏操作,(在下次调用refresh时可以重现屏幕原文) int clrtobot(void); //清除光标位置到屏幕结尾的内容 int clrtoeol(void); //清除光标位置到该行行尾的内容

移动光标;

int move(int new_y, int new_x); //移动stdcsr的光标位置

字符属性(指对字符设置加粗,反色显示等);

预定义的属性:A_BLINK, A_BOLD, A_DIM, A_REVERSE, A_STANDOUT, A_UNDERLINE.

int attron(chtype attribute); int attroff(chtype attribute); //启用或关闭某属性 int attrset(chtype attribute); int standout(void); int standend(void); //这两个表示更加通用的强调模式,通常映射为反白显示

以游客身份体验一下即将绝迹的远古时代的 BBS

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

在这里插入图片描述
在这里插入图片描述

Ubuntu 安装curses库

指导书中的方法没成功,就找到了另一种
具体参考.
1.下载离线安装包
2.上传并解压
3…/configure --with-shared --prefix=/home(自己定义地址就行)
4.make
5.make install
在这里插入图片描述

Linux 环境下C语言编译实现贪吃蛇游戏

mkdir testSnake # 新建文件夹
cd testSnake # 进入该文件

复制实例代码保存为mysnake.c

gedit mysnake.c
gcc mysnake.c -lcurses -o mysnake # 编译链接生成可执行文件
./mysnake

在这里插入图片描述

运行程序:在这里插入图片描述

总结

这次踩了个terminal路径的小坑,和一个curses安装包的大坑(没联网)。浪费了很多时间,也让我知道了快照的重要性,但是我很满足,凭着查到的资料解决了它,现在踩小坑,换以后不踩大坑,我觉得值得

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值