嵌入式作业(三):编译器背后的故事

目录

一、可执行程序的组装

1.1 用gcc生成.a静态库和.so动态库(test1)

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

创建作业目录:

mkdir test1       //创建test1文件夹
ls                //显示文件及目录 
cd test1          //进入目录test1

在这里插入图片描述
用 vim创建hello.h、hello.c 和 main.c3 个文件。

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

gcc -c hello.c
ls                                   //运行ls命令查看是否产生hello.o

在这里插入图片描述

(3)由.o文件创建静态库

ar -crv libmyhello.a hello.o                //创建静态库
ls                                                       //运行ls命令查看结果

在这里插入图片描述

(4)在程序中使用静态库

在 gcc 命令生成目标文件时指明静态库名,gcc 将会从静态库中将公用函数连接到目标文件中

gcc main.c libmyhello.a -o hello 

在这里插入图片描述
静态库中的函数已经连接到目标文件中,在执行文件时,就已经不需要静态库以及hello.c与hello.h
在这里插入图片描述
用方法一时,过程中出现以上问题,原因是指导书的“-lmyhello”,“-”是中文符号。

(5) 由.o 文件创建动态库文件

# gcc -shared -fPIC -o libmyhello.so hello.o //创建静态库
ls                                           //运行ls命令查看结果

在这里插入图片描述
错误:
/usr/bin/ld: hello.o: relocation R_X86_64_32 against `.rodata’ can not be used when making a shared object; recompile with -fPIC
hello.o: 无法添加符号: 错误的值
解决方法:
(1)重新编译 hello.o

gcc -c -fPIC hello.c

然后用这个hello.o生成动态库就不会报错
(2)直接将代码改为

gcc -shared -fPIC -o libmyhello.so hello.c

(也是从.c文件重新编译)

(6)在程序中使用动态库;

和静态库一样,用 gcc 命令生成目标文件时指明动态库名,进行编译,就可以生成目标文件

gcc main.c libmyhello.so -o hello     
./hello

在这里插入图片描述
错误提示:找不到动态库文件 libmyhello.so。
程序在运行时,会在/usr/lib 和/lib 等目录中查找需要的动态库文件。若找到,则载入动态库,否则将提示类似上述错误而终止程序运行。
而我的动态库文件在text1里,所以报错
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
将文件 libmyhello.so 复制到目录/usr/lib 中——>权限不够——>登录管理员模式——>移动成功,hello程序执行成功

(7)静态库和动态库同名

比较当静态库和动态库同名时,gcc 命令会使用哪个库文件
按照以上步骤,重新创建静态库(.a)和动态库(.so)文件(.so文件不用移目录)
在这里插入图片描述
在这里插入图片描述
运行hello程序后,出现错误,说明gcc先使用动态库

(8)小结

**静态库:**在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库。
**动态库:**在程序编译时并不会被连接到目标代码中,在程序运行是才被载入
静态库文件名的命名规范是以 lib 为前缀,紧接着跟静态库名,扩展名为.a
动态库文件名命名规范和静态库文件名命名规范类似,也是在动态库名增加前缀 lib,文件扩展名为.so
-fPIC: 创建与地址无关的编译程序(pic,position independent code),是为了能够在多个应用程序间共享。
-shared: 生成共享目标文件。通常用在建立共享库时
**ar:**主要用于创建静态库

1.2 用gcc生成.a静态库和.so动态库(test2)

编辑作业程序 a1.c 、a2.c、a.h和test.c

创建作业目录:

mkdir test2       //创建test1文件夹
cd test2          //进入目录test1

用 vim创建 a1.c 、a2.c、a.h和test.c 4个文件
在这里插入图片描述
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

test2.c

#include <stdlib.h>
#include "A.h"
int main(){
print1(1);
print2("test");
exit(0);
}

(1)静态库.a 文件的生成与使用

1.1 生成目标文件a1.o、a2.o

gcc -c a1.c a2.c          //用gcc生成.o文件
ls                        //查看

在这里插入图片描述
1.2 生成静态库文件liba.a

ar crv liba.a a1.o a2.o    //由a1.o和a2.o生成静态库文件
ls                         //查看

在这里插入图片描述
1.3 使用.a 库文件,创建可执行程序

gcc -o test test.c libafile.a
./test

出现以下错误:
在这里插入图片描述
原因是我把文件名改为小写的,只需修改test.c相应部分即可
在这里插入图片描述

(2)共享库.so 文件的生成与使用

2.1 生成目标文件a1.o / a2.o( 此处生成.o 文件必须添加"-fpic")

gcc -c -fpic a1.c a2.c

2.2 生成共享库.so 文件

gcc -shared *.o -o libso.so

2.3 使用.so 库文件,创建可执行程序

gcc -o test test.c libso.so 
./test

在这里插入图片描述
会出现和上题同样的错误,解决方法一样
在这里插入图片描述

2.1 在作业1的基础上建立静态库

(1)编辑生成例子程序 b.h、b1.c、b2 和 main.c

b1.c(a和b相加)

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

b2.c(a和b相乘)

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

b.h

#ifndef b_H
#define b_H
float x2x(int a,int  b);
float x2y(int a,int  b);
#endif

main.c

#include <stdlib.h>
#include "b.h"
int main()
{
int a=0,b=0;
scanf("%d%d",&a,&b);
printf("%d+%d=%f/n",a,b,x2x(a,b));

printf("%d+%d=%f/n",a,b,x2y(a,b));

exit(0);
}

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

(2)将 b1.c/b2.c 编译成.o 文件

gcc -c b1.c b2.c

在这里插入图片描述

(3)生成静态库文件libb.a

ar crv libb.a b1.o b2.o    //由a1.o和a2.o生成静态库文件
ls                         //查看

在这里插入图片描述

(4)使用.a 库文件,创建可执行程序

gcc -o main main.c libb.a
./test

在这里插入图片描述

(5)静态库文件大小

在这里插入图片描述

3.1 在作业1的基础上建立静态库

(1)编辑生成例子程序 b.h、b1.c、b2 和 main.c(2.1已完成)

(2)将 b1.c/b2.c 编译成.o 文件( 此处生成.o 文件必须添加"-fpic")

gcc -c -fpic b1.c b2.c

在这里插入图片描述

(3)生成静态库文件libb.so

ar crv libb.so b1.o b2.o    //由a1.o和a2.o生成静态库文件
ls                         //查看

在这里插入图片描述

(4)使用.a 库文件,创建可执行程序

gcc -o main main.c libb.so
mv libb.so /usr/libsi
./main

在这里插入图片描述

(5)动态库文件大小

在这里插入图片描述
在这里插入图片描述
我的程序,动态库与静态库,可执行文件大小一样,libb.so libb.a大小也相同(可能是程序过于简单)

二、gcc编译工具中各软件的用途

1.1 Linux GCC常用命令

(1)简单的编译过程

1.1直接生成可执行文件
(以下的test我都是用的zxx)

#include <stdio.h>
int main(void)
{
printf("Hello World!\n");
return 0;
}
gcc zxx.c -o test

在这里插入图片描述
述编译过程是分为四个阶段进行的,即预处理(也称预编译,Preprocessing)、编译(Compilation)、汇编 (Assembly)和连接(Linking)

1.2 预编译(Preprocessing)

gcc -E test.c -o test.i  //生成预处理文件,test.i
gcc -E test.c            //将预处理文件显示在命令行中       

第一种:
在这里插入图片描述
第二种:
在这里插入图片描述

生成的是一段很长的看不懂的代码;
这就是预处理后的代码
gcc 的-E 选项,可以让编译器在预处理后停止,并输出预处理结果。

1.3 编译–编码为汇编语言(Compilation)
预处理之后,可直接对生成的 test.i 文件编译,生成汇编代码:

gcc -S test.i -o test.s

gcc 的-S 选项,表示在程序编译期间,在生成汇编代码后,停止,-o 输出汇编代码文件。

在这里插入图片描述
1.4 汇编(Assembly)
对于上一题中生成的汇编代码文件 test.s,
gas 汇编器负责将其编译为目标文件,如下:

gcc -c test.s -o test.o

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

gcc test.o -o test

在这里插入图片描述
(test是1.1直接生成,zxx是一步一步生成,源程序没有差别)

(2)多个程序文件的编译

gcc可以同时编译多个源文件

gcc test1.c test2.c -o test

如果同时处理的文件不止一个,GCC 仍然会按照预处理、编译和链接的过程依次进行
上面这条命令大致相当于依次执行如下三条命令:

gcc -c test1.c -o test1.o
gcc -c test2.c -o test2.o
gcc test1.o test2.o -o test

(3)检错

常用的gcc检错命令有以下两种:

gcc -pedantic illcode.c -o illcode
gcc -Wall illcode.c -o illcode

在编译程序时带上-Werror 选项,那么 GCC 会在所有产生警告的地方停止编译,如下:

gcc -Werror test.c -o test

(4)链接

有目标文件链接成可执行文件:

gcc –L /usr/dev/mysql/lib –lmysqlclient test.o –o test   //这里的路径,若不写,则默认在/usr/lib

若想让GCC 在链接时只用到静态链接库,可以使用下面的命令:

gcc –L /usr/dev/mysql/lib –static –lmysqlclient test.o –o test  //-static 静态

也可以直接写出要使用的库如:

gcc -o main main.c libtest.a

1.2 GCC编译器背后的故事

(1)准备工作

#include <stdio.h>
//此程序很简单,仅仅打印一个 Hello World 的字符串。
int main(void)
{
printf("Hello World! \n");
return 0;
}

在这里插入图片描述

(2) 编译过程

1.1已经详细的介绍了gcc的编译过程,这里不再赘叙
直接gcc hello.c -o hello
在这里插入图片描述

(3)分析 ELF 文件

1.ELF 文件的段
位于 ELF Header 和 Section Header Table 之间的都是段(Section)。
一个典型的 ELF 文件包含下面几个段:

.text:已编译程序的指令代码段。
.rodata:ro 代表 read only,即只读数据(譬如常数 const)。
.data:已初始化的 C 程序全局变量和静态局部变量。
.bss:未初始化的 C 程序全局变量和静态局部变量。
.debug:调试符号表,调试器用此段的信息帮助调试。

可以使用 readelf -S 查看其各个 section 的信息如下:

readelf -S hello

在这里插入图片描述
2.反汇编 ELF
由于 ELF 文件无法被当做普通文本文件打开,如果希望直接查看一个 ELF 文件包含的指令和数据,需要使用反汇编的方法。
使用 objdump -D 对其进行反汇编如下:

objdump -D hello

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

gcc -o hello -g hello.c //要加上-g 选项
objdump -S hello

在这里插入图片描述

2.1 nasm的使用

NASM是一个为可移植性与模块化而设计的一个80x86的汇编器。它支持相当多的目标文件格式,包括Linux和’‘NetBSD/FreeBSD’’,’‘a.out’’,’‘ELF’’,’‘COFF’’,微软16位的’‘OBJ’‘和’‘Win32’’。

(1)安装nasm

在这里插入图片描述

(2)下载asm文件

在linux里打开学习通,下载hello.asm

(3)将asm编译为可执行文件

在这里插入图片描述

(4)查看可执行文件大小

在这里插入图片描述

(5)与c语言编译文件大小比较

在这里插入图片描述

(6)小结

nasm编译的可执行程序大小远远小于c程序。
C文件生成可执行文件,会经过预处理,编译阶段编译成汇编指令,再经过汇编阶段变成二进制文件,之后再链接阶段,形成可运行文件。
NASM只是将asm文件经过汇编阶段变成PE或ELF或COFF等二进制文件,并没有进行链接

三、 使用第三方库完成代码设计

1.1 curses的主要函数功能

转载自:Curses函数说明(SCO)

(1)字符显示

WINDOW* initscr() 
  SCREEN* newterm(char *type, FILE *outfd, FILE *infd) 
初始化函数,对用户访问的每个终端都应该调用newterm,type是终端的名称,包括在$TERM(如ansi, xterm, vt100等等) 。 

  SCREEN* set_term(SCREEN* new) 
  用户可以切换当前终端。所有的函数都将在设置的当前终端上起作用。 

  int endwin() 
  退出程序之前,关闭所有打开的窗口。之后还可以调用refresh()。 

  int delwin(win) 
  它删除窗口win。如果存在子窗口,以前先要删除这些子窗口。这个函数将释放win所占据的所有资源。在调用endwin()之前用户应该删除所有的窗口。

(2)输出选项

int  idlok(win, bf) 
  void fdcok(win, bf) 
  这两个函数为窗口使能或者关闭终端的insert/delete特征(idlok(.)针对一行,而fdcok(.)则针对字符)(注:idcok(.)尚未实现) 

  void immedok(win, bf) 
  bf设置为TRUE,则对窗口win的每一次改变都将导致物理屏幕的一次刷新。 这将使程序的性能降低,所以默认的值是FALSE。(注:此函数尚未实现) 

  int clearok(win, bf) 
  如果bf值为TRUE,则下一次调用wrefresh(win)时将会清除屏幕, 并完全地把它重新画一遍(就像用户在编辑器vi中按下Ctrl+L一样)。 

  int leaveok(win, bf) 
  默认的行为是,ncurses让物理光标停留在上次刷新窗口时的同一个位置上。 
  不使用光标的程序可以把leaveok(.)设置为TRUE,这样一般可以节省光标移动所需要的时间。
  此外,ncurses将试图使终端光标不可见。 

(3)输入选项

 int keypad(win, bf) 
  bf为TRUE,函数在等待输入时会使能用户终端的键盘上的小键盘。 
  ncurses将返回一个键代码,该代码在.h头文件中被定义为KEY_*宏,它是针对小键盘上的功能键和方向键的。
  对于PC键盘来说,这一点是非常有帮助的,因为这样用户就可以使能数字键和光标键。 

  int meta(win.bf) 
  bf为TRUE,从getch()返回的键代码将是完整的8位(最高位将不会被去掉) 

  int cbreak() 
  int nocbreak() 
  int crmode() 
  int nocrmode() 
  cbreak()和nocbreak()将把终端的CBREAK模式打开或关闭。
  如果CBREAK打开则程序就可以立刻使用读取的输入信息。
  如果CBREAK关闭,则输入将被缓存起来,直到产生新的一行
  (注意:crmode()和nocrmode()只是为了提供向上兼容性,不要使用它们) 

2.1 在 win10 系统中体验BBS

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

3.1 安装curses库及头文件位置

安装代码:
sudo apt-get install libncurses5-dev 

头文件在/usr/include里面
在这里插入图片描述
库文件在/usr/lib

4.1curses库实现的贪吃蛇游戏

贪吃蛇代码http://www.linuxidc.com/Linux/2011-08/41375.htm

vim aaa.c
gcc aaa.c -lcurses -o a
/.a

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值