嵌入式程序设计学习(3)

学习目标

  1. 请说明可执行程序是如何被组装的?
    (1)阅读、理解和学习材料“用gcc生成静态库和动态库.pdf”和“静态库.a与.so库文件的生成与使用.pdf”,请在Linux系统(Ubuntu)下如实仿做一遍。
    (2)在第一次作业的程序代码基础进行改编,除了x2x函数之外,再扩展写一个x2y函数(功能自定),main函数代码将调用x2x和x2y ;将这3个函数分别写成单独的3个 .c文件,并用gcc分别编译为3个.o 目标文件;将x2x、x2y目标文件用 ar工具生成1个 .a 静态库文件, 然后用 gcc将 main函数的目标文件与此静态库文件进行链接,生成最终的可执行程序,记录文件的大小。
    (3)将x2x、x2y目标文件用 ar工具生成1个 .so 动态库文件, 然后用 gcc将 main函数的目标文件与此动态库文件进行链接,生成最终的可执行程序,记录文件的大小,并与之前做对比。

  2. Gcc不是一个人在战斗。请说明gcc编译工具集中各软件的用途,了解EFF文件格式,汇编语言格式。
    (1)阅读、理解和学习材料“Linux GCC常用命令.pdf”和“GCC编译器背后的故事.pdf”,如实仿做一遍。
    (2)as汇编编译器针对的是AT&T汇编代码风格,Intel风格的汇编代码则可以用nasm汇编编译器编译生成执行程序。请在ubuntu中下载安装nasm,对示例代码“hello.asm”编译生成可执行程序,并与“hello world”C代码的编译生成的程序大小进行对比。

  3. 每一个程序背后都站着一堆优秀的代码库。了解实际程序是如何借助第三方库函数完成代码设计。
    (1)了解Linux 系统中终端程序最常用的光标库(curses)的主要函数功能,写出几个基本函数名称及功能;
    (2)在 win10 系统中,“控制面板”–>“程序”—>“启用或关闭Windows功能”,启用 “telnet client” 和"适用于Linux的Windows子系统"(后面会使用)。 然后打开一个cmd命令行窗口,命令行输入 telnet bbs.newsmth.net,以游客身份体验一下即将绝迹的远古时代的 BBS (一个用键盘光标控制的终端程序)。
    (3)在Ubuntu中用 sudo apt-get install libncurses5-dev 安装curses库,请说明 头文件(比如curses.h)和库文件都被安装到哪些目录中;
    (4)请参考 “Linux 环境下C语言编译实现贪吃蛇游戏”(http://www.linuxidc.com/Linux/2011-08/41375.htm)或者弹球游戏(https://blog.csdn.net/psc0606/article/details/9990981),用gcc编译生成一个终端游戏,体会curses库如何被链接和使用。

练习gcc生成静态库和动态库

  1. 创建一个 test1 文件夹,并在该文件夹中创建三个子程序 hello.h、hello.c 和 main.c
mkdir test1
cd test1
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;
}

在这里插入图片描述

  1. 生成hello.o文件
    在这里插入图片描述

  2. 创建静态库

ar -crv libmyhello.a hello.o

在这里插入图片描述

  1. 使用静态库
gcc -o hello main.c -L. –lmyhello

自定义的库时,main.c 还可放在-L.和 –lmyhello 之间,但是不能放在它俩之后,否则会提 示 myhello 没定义,但是是系统的库时,如 g++ -o main(-L/usr/lib) -lpthread main.cpp 就不出错。
在这里插入图片描述

  1. 由.o文件创建动态库文件
    动态库文件名命名规范和静态库文件名命名规范类似,也是在动态库名增加前缀 lib,但其 文件扩展名为.so。用 gcc 来创建动态库。 在系统提示符下键入以下命令得到动态库文件 libmyhello.so。
    在这里插入图片描述
    在程序中使用动态库和使用静态库完全一样,也是在使用到这些公用函数的源程序中包含 这些公用函数的原型声明,然后在用 gcc 命令生成目标文件时指明动态库名进行编译。我 们先运行 gcc 命令生成目标文件,再运行它看看结果。
    在这里插入图片描述
    (或 #gcc main.c libmyhello.so -o hello 不会出错(没有 libmyhello.so 的话,会出错),但是 接下来./hello 会提示出错,因为虽然连接时用的是当前目录的动态库,但是运行时,是到 /usr/lib 中找库文件的,将文件 libmyhello.so 复制到目录/usr/lib 中就 OK 了)
    在这里插入图片描述
    快看看错误提示,原来是找不到动态库文件 libmyhello.so。程序在运行时, 会在/usr/lib 和/lib 等目录中查找需要的动态库文件。若找到,则载入动态库,否则将提 示类似上述错误而终止程序运行。我们将文件 libmyhello.so 复制到目录/usr/lib 中,再试试。
    如果不行我们加入sudo权限
    在这里插入图片描述
    我们回过头看看,发现使用静态库和使用动态库编译成目标程序使用的 gcc 命令完全一样, 那当静态库和动态库同名时,gcc 命令会使用哪个库文件呢?抱着对问题必究到底的心情, 来试试看。
    先删除除.c 和.h 外的所有文件,恢复成我们刚刚编辑完举例程序状态。
    在这里插入图片描述
    在这里插入图片描述
    通过上述最后一条 ls 命令,可以发现静态库文件 libmyhello.a 和动态库文件 libmyhello.s o 都已经生成,并都在当前目录中。然后,我们运行 gcc 命令来使用函数库 myhello 生成目 标文件 hello,并运行程序 hello。
    在这里插入图片描述
    (动态库和静态库同时存在时,优先使用动态库,当然,如果直接 gcc main.c libmyhello.a -o hello 的话,就是指定为静态库了)
    运行的结果中很容易知道,当静态库和动态库同名时,gcc 命令将优先使用动 态库,默认去连/usr/lib 和/lib 等目录中的动态库,将文件 libmyhello.so 复制到目录/usr/lib 中即可。

自己编写函数实现静态库和动态库

  1. 写入main.c
    在这里插入图片描述
    sub1.c

在这里插入图片描述
sub2.c
在这里插入图片描述
sub.h
在这里插入图片描述

  1. 使用gcc分别编译sub1.c和sub2.c生成sub1.o和sub2.o
    在这里插入图片描述

静态库

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

  2. 键入以下语句生成可执行文件
    gcc -o main main.c libmysub.a
    在这里插入图片描述

  3. 静态库文件大小
    在这里插入图片描述

动态库

  1. 生成动态库文件
    必须先键入gcc -c -fpic sub1.c sub2.c不然在生成动态库时会报错
    生成动态库语句:gcc -shared -o libmysub.so sub1.o sub2.o
    在这里插入图片描述

  2. 将main.c与动态库链接并运行发现报错因为在默认目录下找不到所创建的动态库文件
    在这里插入图片描述
    将动态库移动到默认目录下并运行
    在这里插入图片描述

  3. 动态库库文件大小
    在这里插入图片描述

Gcc不是一个人在战斗

Gcc常用命令

  1. 生成一个示例文本
    在这里插入图片描述
  2. 一步到位的编译指令
gcc test.c -o test

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

gcc -E test.c -o test.i

可以输出 test.i 文件中存放着 test.c 经预处理之后的代码。打开 test.i 文件,看一看,就明白了。后 面那条指令,是直接在命令行窗口中输出预处理后的代码。
在这里插入图片描述
预处理之后,可直接对生成的 test.i 文件编译,生成汇编代码: gcc -S test.i -o test.sgcc 的-S 选项,表示在程序编译期间,在生成汇编代码后,停止,-o 输出汇编代码文件。
在这里插入图片描述
对于上一小节中生成的汇编代码文件 test.s,gas 汇编器负责将其编译为目标文件,如下:

gcc -c test.s -o test.o

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

gcc test.o -o test

在这里插入图片描述

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

  1. 安装nasm
sudo apt install nasm
  1. 创建一个hello.asm文件使用nasm对hello.asm编译生成hello.o文件,使用ld -s -o hello hello.o生成一个可执行文件hello,并执行
    在这里插入图片描述
  2. 与c语言所生成程序作比较
    在这里插入图片描述

其他函数库

1.curse的基本常用函数
initscr(): initscr() 是一般 curses 程式必须先呼叫的函数, 一但这个函数被呼叫之後, 系统将根据终端机的形态并启动 curses 模式.

endwin(): curses 通常以呼叫 endwin() 来结束程式. endwin() 可用来关闭curses 模式, 或是暂时的跳离 curses 模式. 如果您在程式中须要call shell ( 如呼叫 system() 函式 ) 或是需要做 system call, 就必须先以 endwin() 暂时跳离 curses 模式. 最後再以wrefresh() doupdate() 来重返 curses 模式.

cbreak() and nocbreak(): 当 cbreak 模式被开启後, 除了 DELETE 或 CTRL 等仍被视为特殊控制字元外一切输入的字元将立刻被一一读取.当处於 nocbreak 模式时, 从键盘输入的字元将被储存在 buffer 里直到输入 RETURN或 NEWLINE.在较旧版的 curses 须呼叫 crmode(),nocrmode() 来取代 cbreak(),nocbreak()

nl() and nonl(): 用来决定当输入资料时, 按下 RETURN 键是否被对应为 NEWLINE 字元 ( 如 /n ). 而输出资料时, NEWLINE 字元是否被对应为 RETURN 和 LINDFEED系统预设是开启的.

echo() and noecho(): 此函式用来控制从键盘输入字元时是否将字元显示在终端机上.系统预设是开启的.
intrflush(win,bf): 呼叫 intrflush 时须传入两个值, win 为一 WINDOW 型态指标, 通常传入标准输出入萤幕 stdscr. bf 为 TRUE 或 FALSE. 当 bf 为 true 时, 当输入中断字元 ( 如 break) 时, 中断的反应将较为快速.但可能会造成萤幕的错乱.

keypad(win,bf): 呼叫 keypad 时须传入两个值, win 为一 WINDOW 型态指标, 通常传入标准输出入萤幕 stdscr. bf 为 TRUE 或 FALSE. 当开启 keypad 後, 可以使用键盘上的一些特殊字元, 如上下左右>等方向键, curses 会将这些特殊字元转换成 curses.h 内定义的一些特殊键. 这些定义的特殊键通常以 KEY_ 开头.

refresh(): refresh() 为 curses 最常呼叫的一个函式. curses 为了使萤幕输出入达最佳化, 当您呼叫萤幕输出函式企图改变萤幕上的画面时, curses 并不会立刻对萤幕做改变, 而是等到refresh() 呼叫後, 才将刚才所做的变动一次完成. 其馀的资料将维持不变. 以尽可能送最少的字元至萤幕上. 减少萤幕重绘的时间.如果是 initscr() 後第一次呼叫 refresh(), curses 将做清除萤幕的工作.

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

移动光标:
int move(int new_y, int new_x); //移动stdcsr的光标位置
int leaveok(WINDOW *window_ptr,bool leave_flag);
//设置一个标志,用于控制在屏幕刷新后curses将物理光标放置的位置。

窗口移动和更新屏幕:
int mvwin(WINDOW *win, int new_y, int new_x); //移动窗口
int wrefresh(WINDOW *win);
int wclear(WINDOW *win);
int werase(WINDOW *win);
//类似于上面的refresh, clear, erase,但是此时针对特定窗口操作,而不是stdcur
int touchwin(WINDOW *win); //指定该窗口内容已改变、
//下次wrefresh时,需重绘窗口。利用该函数,安排要显示的窗口
int scrollok(WINDOW *win, bool flag); //指定是否允许窗口卷屏
int scroll(WINDOW *win); //把窗口内容上卷一行
2.安装curse库函数

sudo apt install libncurses5-dev

在这里插入图片描述
3.curses.h所在目录
在这里插入图片描述
在这里插入图片描述
4.终端游戏俄罗斯方块

sudo apt-get install bastet//安装俄罗斯方块

bastet//启动游戏

在这里插入图片描述

学习小结

不难,就是有点繁琐。

参考文献

http://www.linuxidc.com/Linux/2011-08/41375.htm
https://blog.csdn.net/psc0606/article/details/9990981

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值