嵌入式系统开发之编译器入门

嵌入式系统开发之编译器入门

目录

一.可执行程序的组装

1.1用gcc生成静态库和动态库

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

先创建一个作业目录,保存本次练习的文件

mkdir test1 
cd test1

然后用 vim、nano 或 gedit 等文本编辑器编辑生成所需要的 3 个文件。 hello.c(见程序 2)是函数库的源程序,其中包含公用函数 hello,该函数将在屏幕上输出"Hello XXX!"。hello.h(见程序 1)为该函数库的头文件。main.c(见程序 3)为测试库文件的主程序, 在主程序中调用了公用函数 hello。

hh@ubuntu:~/test1$ vim hello.h
hh@ubuntu:~/test1$ vim hello.c
hh@ubuntu:~/test1$ vim main.c
2)第 2 步:将 hello.c 编译成.o 文件。

无论静态库,还是动态库,都是由.o 文件创建的。因此,我们必须将源程序 hello.c 通过 g cc 先编译成.o 文件。在系统提示符下键入以下命令得到 hello.o 文件。

hh@ubuntu:~/test1$ gcc -c hello.c
hh@ubuntu:~/test1$ ls
hello.c  hello.h  hello.o  main.c
3)第 3 步:由.o 文件创建静态库。

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

hh@ubuntu:~/test1$ ar -crv libmyhello.a hello.o
a - hello.o
hh@ubuntu:~/test1$ ls
hello.c  hello.h  hello.o  libmyhello.a  main.c
4)第 4 步:在程序中使用静态库。

静态库制作完了,如何使用它内部的函数呢?只需要在使用到这些公用函数的源程序中包 含这些公用函数的原型声明,然后在用 gcc 命令生成目标文件时指明静态库名,gcc 将会从 静态库中将公用函数连接到目标文件中。注意,gcc 会在静态库名前加上前缀 lib,然后追 加扩展名.a 得到的静态库文件名来查找静态库文件。 在程序 3:main.c 中,我们包含了静态库的头文件 hello.h,然后在主程序 main 中直接调用 公用函数 hello。下面先生成目标程序 hello,然后运行 hello 程序看看结果如何。
方法一:

hh@ubuntu:~/test1$ gcc -o hello main.c -L. –lmyhello

方法二:

hh@ubuntu:~/test1$ gcc main.c libmyhello.a -o hello

方法三:

hh@ubuntu:~/test1$ gcc -c main.c
hh@ubuntu:~/test1$ gcc -o hello main.o libmyhello.a

运行

hh@ubuntu:~/test1$ ./hello
Hello everyone!

我们删除静态库文件试试公用函数 hello 是否真的连接到目标文件 hello 中了。

hh@ubuntu:~/test1$ rm libmyhello.a
hh@ubuntu:~/test1$ ./hello
Hello everyone!

5)第 5 步:由.o 文件创建动态库文件。

动态库文件名命名规范和静态库文件名命名规范类似,也是在动态库名增加前缀 lib,但其 文件扩展名为.so。例如:我们将创建的动态库名为 myhello,则动态库文件名就是 libmyh ello.so。用 gcc 来创建动态库。 在系统提示符下键入以下命令得到动态库文件 libmyhello.so。

hh@ubuntu:~/test1$ gcc -shared -fPIC -o libmyhello.so hello.o
6)第 6 步:在程序中使用动态库

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

hh@ubuntu:~/test1$ gcc -o hello main.c -L. -lmyhello

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

hh@ubuntu:~/test1$ mv libmyhello.so /usr/lib

成功了。这也进一步说明了动态库在程序运行时是需要的。 我们回过头看看,发现使用静态库和使用动态库编译成目标程序使用的 gcc 命令完全一样, 那当静态库和动态库同名时,gcc 命令会使用哪个库文件呢?抱着对问题必究到底的心情, 来试试看。 先删除除.c 和.h 外的所有文件,恢复成我们刚刚编辑完举例程序状态。

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

准备工作

1)先创建一个作业目录,保存本次练习的文件。
hh@ubuntu:~$ mkdir test2
hh@ubuntu:~$ cd text2
hh@ubuntu:~/text2$ 
2)然后用 vim、nano 或 gedit 等文本编辑器编辑生成所需要的四个文件 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

#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); }

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

1)生成目标文件(xxx.o)
hh@ubuntu:~/text2$ gcc -c A1.c A2.c
2)生成静态库.a 文件
hh@ubuntu:~/text2$ ar crv libafile.a A1.o A2.o
r - A1.o
r - A2.o
3)使用.a 库文件,创建可执行程序(若采用此种方式,需保证生成的.a 文件与.c 文件保 存在同一目录下,即都在当前目录下)
hh@ubuntu:~/text2$ gcc -o test test.c libafile.a
hh@ubuntu:~/text2$ ./test
A1 print arg:1
A2 printf arg:test

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

1)、生成目标文件(xxx.o)(此处生成.o 文件必须添加"-fpic"(小模式,代码少),否则在生成.so 文件时会出错
hh@ubuntu:~/text2$  gcc -c -fpic A1.c A2.c
2)生成共享库.so 文件
hh@ubuntu:~/text2$  gcc -shared *.o -o libsofile.so
3)使用.so 库文件,创建可执行程序
hh@ubuntu:~/text2$ ./test
A1 print arg:1
A2 printf arg:test

2.尝试自己使用静态库

1)创建三个文件
hh@ubuntu:~$ gcc -c sub2.c
hh@ubuntu:~$ vim sub2.c
hh@ubuntu:~$ vim main,c
hh@ubuntu:~$ vim main.c

2)并用gcc分别编译为3个.o 目标文件
hh@ubuntu:~$ gcc -c sub2.c
hh@ubuntu:~$ gcc -c main.c
hh@ubuntu:~$ ls
hcr       main1.c   Music     sub1.o  Templates  Videos
Documents         hellow    main.c    Pictures  sub2.c  test1
Downloads         hellow.c  main.o    Public

3)将x2x、x2y目标文件用 ar工具生成1个 .a 静态库文件, 然后用 gcc将 main函数的目标文件与此静态库文件进行链接
hcr@ubuntu:~$ ar crv libafile.a sub2.o sub3.o
a - sub2.o
a - sub3.o
hcr@ubuntu:~$ gcc -o main main.c libafile.a
4)将x2x、x2y目标文件用 ar工具生成1个 .a 静态库文件, 然后用 gcc将 main函数的目标文件与此静态库文件进行链接,生成最终的可执行程序
h2=x2y(a,b);      //调用sub1里的x2x
    ^
hcr@ubuntu:~$ ./main
45.000000
14.000000
5)记录文件大小

这里使用du -sh命令

hcr@ubuntu:~$ du -sh main
12K	main

3.尝试自己使用动态库

1)生成目标文件(xxx.o)(此处生成.o 文件必须添加"-fpic"(小模式,代码少),否则在生成.so 文件时会出错)
hcr@ubuntu:~$ gcc -c -fpic sub2.c sub3.c
2)生成共享库.so 文件
hcr@ubuntu:~$ gcc -shared -fPIC -o libmyhello.so sub2.o sub3.o
3)使用.so 库文件,创建可执行程序
hcr@ubuntu:~$  gcc -o main1 main.c -L. -lmyhello
4)记录文件大小

这里使用du -sh命令

hcr@ubuntu:~$ du -sh  main1
12K	main1

二. Gcc不是一个人在战斗。请说明gcc编译工具集中各软件的用途,了解EFF文件格式,汇编语言格式。

2.1Linux GCC 常用命令

1.简介

GCC 的意思也只是 GNU C Compiler 而已。经过了这么多年的发展,GCC 已经不仅仅能支持 C 语言;它现在还支持 Ada 语言、C++ 语言、Java 语言、Objective C 语言、Pascal 语言、COBOL 语言,以及支持函数式编程和逻辑编程的 Mercury 语言,等等。而 GCC 也不再单只是 GNU C 语 言编译器的意思了,而是变成了 GNU Compiler Collection 也即是 GNU 编译器家族的意思了。另 一方面,说到 GCC 对于操作系统平台及硬件平台支持,概括起来就是一句话:无所不在。

2. 简单编译

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

 #include <stdio.h> 
 int main(void)
  {
    
     printf("Hello World!\n"); return 0; }
1)预处理
hcr@ubuntu:~/text1$ vim test.c
hcr@ubuntu:~/text1$ gcc -E test.c -o test.i

可以输出 test.i 文件中存放着 test.c 经预处理之后的代码。打开 test.i 文件,看一看,就明白了。后 面那条指令,是直接在命令行窗口中输出预处理后的代码. gcc 的-E 选项,可以让编译器在预处理后停止,并输出预处理结果。在本例中,预处理结果就是将 stdio.h 文件中的内容插入到 test.c 中了。

2)2 编译为汇编代码(Compilation)

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

hcr@ubuntu:~/text1$ gcc -S test.i -o test.s

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

3) 汇编(Assembly)

对于上一小节中生成的汇编代码文件 test.s,gas 汇编器负责将其编译为目标文件,如下:

hcr@ubuntu:~/text1$ gcc -c test.s -o test.o
4)连接(Linking)

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

hcr@ubuntu:~/text1$ gcc test.o -o test
hcr@ubuntu:~/text1$ ./test
Hello World!

2.2GCC 编译器背后的故事

1.GCC: GCC(GNU C Compiler)是编译工具。本文所要介绍的将 C/C++语言编写的程序 转换成为处理器能够执行的二进制代码的过程即由编译器完成。
2.Binutils: 一组二进制程序处理工具,包括:addr2line、ar、objcopy、objdump、as、ld、 ldd、readelf、 size 等。这 一组工具 是开发和 调试不可 缺少的工具
3.C 运行库:C 语言标准主要由两部分组成:一部分描述 C 的语法,另一部分描述 C 标准库。 C 标准库定义了一组标准头文件,每个头文件中包含一些相关的函数、变量、类 型声明和宏定义,譬如常见的 printf 函数便是一个 C 标准库函数,其原型定义 在 stdio 头文件中。 C 语言标准仅仅定义了 C 标准库函数原型,并没有提供实现。因此,C 语言编译 器通常需要一个 C 运行时库(C Run Time Libray,CRT)的支持。C 运行时库又 常简称为 C 运行库。与 C 语言类似,C++也定义了自己的标准,同时提供相关支 持库,称为 C++运行时库。

1.准备工作

由于 GCC 工具链主要是在 Linux 环境中进行使用,因此本文也将以 Linux 系统作 为工作环 境。为了能够 演示编译的整个 过程,先创建一 个工作目录 test0,然后 用文本编辑器生成一个 C 语言编写的简单 Hello.c 程序为示例,其源代码如下所 示

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

2.编译过程

1).预处理

预处理的过程主要包括以下过程:
(1) 将所有的#define 删除,并且展开所有的宏定义,并且处理所有的条件预编 译指令,比如#if #ifdef #elif #else #endif 等。
(2) 处理#include 预编译指令,将被包含的文件插入到该预编译指令的位置。
(3) 删除所有注释“//”和“/* */”。
(4) 添加行号和文件标识,以便编译时产生调试用的行号及编译错误警告行号。
(5) 保留所有的#pragma 编译器指令,后续编译过程需要使用它们。

hcr@ubuntu:~/text1$ vim hello.c
hcr@ubuntu:~/text1$ gcc -E hello.c -o hello.i

2 )编译

编译过程就是对预处理完的文件进行一系列的词法分析,语法分析,语义分析及 优化后生成相应的汇编代码。 将预处理生成的 hello.i 文件编译生成汇编程序 hello.s // GCC 的选项-S 使 GCC 在执行完编译后停止,生成汇编程序

hcr@ubuntu:~/text1$ gcc -S hello.i -o hello.s
3)汇编

汇编过程调用对汇编代码进行处理,生成处理器能识别的指令,保存在后缀为.o 的目标文件中。由于每一个汇编语句几乎都对应一条处理器指令,因此,汇编相 对于编译过程比较简单,通过调用 Binutils 中的汇编器 as 根据汇编指令和处理 器指令的对照表一一翻译即可。 当程序由多个源代码文件构成时,每个文件都要先完成汇编工作,生成.o 目标 文件后,才能进入下一步的链接工作。注意:目标文件已经是最终程序的某一部 分了,但是在链接之前还不能执行

hcr@ubuntu:~/text1$ gcc -c hello.s -o hello.o
4).链接

链接也分为静态链接和动态链接,其要点如下:
(1) 静态链接是指在编译阶段直接把静态库加入到可执行文件中去,这样可执行 文件会比较大。链接器将函数的代码从其所在地(不同的目标文件或静态链 接库中)拷贝到最终的可执行程序中。为创建可执行文件,链接器必须要完 成的主要任务是:符号解析(把目标文件中符号的定义和引用联系起来)和 重定位(把符号定义和内存地址对应起来然后修改所有对符号的引用)。 (2) 动态链接则是指链接阶段仅仅只加入一些描述信息,而程序执行时再从系统 中把相应动态库加载到内存中去。

hcr@ubuntu:~/text1$ gcc hello.c -o hello

2.3nasm

对示例代码“hello.asm”编译生成可执行程序,并与“hello world”C代码的编译生成的程序大小进行对比

1.简单安装nasm

1)获取安装文件

输入命令sudo apk-get install nasm

```bash
hcr@ubuntu:~/text1$ sudo apk-get install nasm
2)输入密码
[sudo] password for hcr: 
sudo: apk-get: command not found
3)自动安装完成

2.使用nasm

1)下载示例文件hello.asw

我是在linux的浏览器里打开学习通,下载的
在这里插入图片描述

2)编译
hcr@ubuntu:~$ nasm -f elf32 hello.asm -o hello.o
hcr@ubuntu:~$ ld -m elf_i386 hello.o -o hello
hcr@ubuntu:~$ ./hello
Hello, world!
3)记录大小

nasm编译的文件大小

hcr@ubuntu:~$ size hello
   text	   data	    bss	    dec	    hex	filename
     34	     14	      0	     48	     30	hello

C代码的编译生成的程序大小

hcr@ubuntu:~/text1$ ./hello
Hello World! 
hcr@ubuntu:~/text1$ size hello
   text	   data	    bss	    dec	    hex	filename
   1184	    552	      8	   1744	    6d0	hello

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

3.1了解Linux 系统中终端程序最常用的光标库(curses)的主要函数功能,写出几个基本函数名称及功能

1.常见的curses函数

1)全局变量

WINDDW* curscr:当前屏幕
  WINDOW* stdscr:标准屏幕
  int   LINES:终端上的行数
  int   COLS:终端上的列数
  bool   TRUE:真标志,1
  bool   FALSE:假标志,0
  int   ERR:错误标志,-1
  int   OK:OK标志,0
  ------------------------------------------------
win:WINDOW *
bf:bool
ch:chty pe
str:char *
chstr:chty pe*
fmt:char *  否则:int

2)终端属性

 int baudrate()
  此函数返回终端的速度,以bps为单位。
  char erasechar()
  此函数返回当前删除的字符。
  char killchar()
  此函数返回当前杀死的字符。
  int has_ic()
  int has_il()
  如果终端具有插入/删除字符的能力,则has_rc()将返回TRUE,如果终端具有插入/删除行的能力,则has_il()将返回TRUE,否则这两个函数将返回ERR。(注:尚未实现)
  char* longname()
  此函数所返回的指针允许用户访问当前终端的描述符。
  chty petermattrs()(注:此函数尚未实现)
  char* termname()
  这个函数从用户环境中返回TERM的内容。(注:此函数尚未实现)

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

1.Windows

1)win10 系统中,“控制面板”–>“程序”—>“启用或关闭Windows功能”

1.在这里插入图片描述

2.在这里插入图片描述
3.
在这里插入图片描述

需要重启电脑

2)启用 “telnet client” 和"适用于Linux的Windows子系统"(后面会使用)。 然后打开一个cmd命令行窗口,命令行输入 telnet bbs.newsmth.net,

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

3.3在Ubuntu中用 sudo apt-get install libncurses5-dev 安装curses库

1)安装curses 库
hcr@ubuntu:~$ sudo apt-get install libncurses5-dev
2)寻找curses库

我们可以发现curse文件在/usr/include里

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

1)用vim创建一个a.c

复制代码进入a.c

2)编译命令:

cc a.c -lcurses -o a.o

3)运行
hcr@ubuntu:~$ ./a.o

运行动图(网页链接在附录)
在这里插入图片描述

四.附录

(http://www.linuxidc.com/Linux/2011-08/41375.htm)或者弹球游戏(https://blog.csdn.net/psc0606/article/details/9990981),用gcc编译生成一个终端游戏,体会curses库如何被链接和使用。

(有空也可玩玩网站 https://blog.p2hp.com/archives/3615 中列举的10个终端游戏。)

五.总结

通过此次的学习,我认为Linux和windows都是可以运用到我们生活中的的操作系统,嵌入式很枯燥,但是我们要将嵌入式学习与Linux结合就会很有意思。

猜你喜欢

转载自blog.csdn.net/nsnsnbabsb/article/details/109093150