〇作业要求
-
请说明可执行程序是如何被组装的?
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函数的目标文件与此动态库文件进行链接,生成最终的可执行程序,记录文件的大小,并与之前做对比。 -
Gcc不是一个人在战斗。请说明gcc编译工具集中各软件的用途,了解EFF文件格式,汇编语言格式。
1)阅读、理解和学习材料“Linux GCC常用命令.pdf”和“GCC编译器背后的故事.pdf”,如实仿做一遍。
2)as汇编编译器针对的是AT&T汇编代码风格,Intel风格的汇编代码则可以用nasm汇编编译器编译生成执行程序。请在ubuntu中下载安装nasm,对示例代码“hello.asm”编译生成可执行程序,并与“hello world”C代码的编译生成的程序大小进行对比。 -
每一个程序背后都站着一堆优秀的代码库。了解实际程序是如何借助第三方库函数完成代码设计。
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库如何被链接和使用。
一、可执行程序是如何被组装的
1.1-1 (例)用gcc生成静态库和动态库
第 1 步:编辑生成例子程序 hello.h、hello.c 和 main.c
第 2 步:将 hello.c 编译成.o 文件
使用gcc指令将 hello.c 编译成.o 文件
gcc -c hello.c
可以看到,当前目录已经生成了hello.o文件
第 3 步:由.o 文件创建静态库
ar -crv libmyhello.a hello.o
用 ar 指令生成了静态库libmyhello.a
第 4 步:在程序中使用静态库
gcc -o hello mainc _L. -lmyhello
运行hello可执行文件,可以看到程序输出正确
第 5 步:由.o 文件创建动态库文件
键入以下命令得到动态库文件 libmyhello.so
gcc -shared -fPIC -o libmyhello.so hello.o
可以看到成功生成动态库文件 libmyhello.so
第 6 步:在程序中使用动态库
用 gcc 命令生成目标文件时指明动态库名进行编译,我们先运行 gcc 命令生成目标文件。
gcc -o hello main.c -L. -lmyhello
我们会发现系统报错,是因为系统找不到动态库文件 libmyhello.so。程序在运行时,会在 /usr/lib 和 /lib 等目录中查找需要的动态库文件。若找到,则载入动态库,否则将提示类似上述错误而终止程序运行。我们将文件 libmyhello.so 复制到目录 /usr/lib 中
mv libmyhello.so /usr/lib
再次生成目标文件成功,直接运行可执行文件hello,成功
当静态库和动态库同名时,gcc 命令会使用哪个库文件呢?
删除除.c、.h文件以外的所有文件
重新创建静态库文件 libmyhello.a 和动态库文件 libmyhello.so
1.ar -cvr libmyhello.a hello.o
2.gcc -shared -fPIC -o libmyhello.so hello.o
可以看到我们重新创建了静态库文件和动态库文件,接下来运行hello程序
gcc -o hello main.c -L. -lmyhello
可以看到,运行后报错,当静态库和动态库同名时,gcc 命令将优先使用动 态库,默认去连 /usr/lib 和 /lib 等目录中的动态库,将文件 libmyhello.so 复制到目录 /usr/lib 中即可成功运行。
1.1-2 (例)静态库.a与.so库文件的生成与使用
第 1 步:创建所需文件
用自己熟悉的文本编辑器编辑生成所需要的四个文件 A1.c 、 A2.c、 A.h、 test.c
第 2 步:静态库.a 文件的生成与使用
用 “1.1-1 用gcc生成静态库和动态库” 的操作方法,依次生成目标文件、静态库文件、可执行文件,成功运行可执行文件,在此不再赘述。
第 3 步:动态库.so 文件的生成与使用
同 “1.1-1 用gcc生成静态库和动态库” 的操作方法,依次生成目标文件、动态库文件、可执行文件,运行可执行文件出错,还是要将动态库文件拷贝到 /usr/lib ,之后程序便可成功运行。
1.2 用自己编写的文件实现生成静态、动态库
1.2-1 使用静态库
第 1 步:创建.c文件
使用自己的代码片:x2x.c、x2y.c、main.c,具体代码如下
//x2x.c
#include<stdio.h>
float x2x(float x)
{
float a;
a = x * x;
return (a);
}
//x2y.c
#include<stdio.h>
float x2y(float x, float y)
{
float b;
b = x * y;
return (b);
}
//main.c
#include"x2x.c"
#include"x2y.c"
#include<stdio.h>
int main()
{
float x=5;
float y=3;
printf("a=%f\n",x2x(x));
printf("b=%f\n",x2y(x,y));
return 0;
}
第 2 步:分别将这三个文件使用gcc编译成.o文件
gcc -c x2x.c
gcc -c x2y.c
gcc -c main.c
可以看到,使用gcc编译后生成了三个.o目标文件
第 3 步:将x2x.c和x2y.c目标文件用 ar 生成1个 .a 静态库文件
ar -crv libmy2xy.a x2x.o x2y.o
可以看到,使用 ar 指令生成了 libmyxy.a 静态库文件
第 4 步:使用gcc将main.o目标文件与 libmyxy.a 静态库文件进行链接
gcc main.c libmyxy.a -o xymain
成功连接了main.o目标文件与 libmyxy.a 静态库文件,生成了可执行文件xymain,运行xymain程序
./xymain
可以看到,程序运行成功!
接下来我们查看一下xymain的大小
xymain的大小为8360B
1.2-2 使用动态库
第 1 步:将x2x、x2y 目标文件用 ar 生成1个 .so 动态库文件
首先删除上一小节中所生成的 libmyxy.a、xymain 文件
rm libmyxy.a xymain
用 ar 指令生成动态库文件
ar -crv libmyxy.so x2x.o x2y.o
成功生成libmyxy.so动态库文件
第 2 步:用 gcc 将 main.o 目标文件与libmyxy.so动态库文件进行链接
gcc main.c libmyxy.so -o xymain
成功生成xymain可执行程序,接下来运行它,并且查看一下它的大小
./xymain
程序成功执行,大小8360B。
与使用静态库时一样,可能是因为程序编写的过于简单的原因。
二、了解gcc编译工具集、EFF文件格式、汇编语言格式
2.1 Linux GCC常用命令
1、准备一个简单的main.c文件
#include<stdio.h>
int main(void)
{
printf("This is Zxs's desktop")
return 0;
}
2、用 gcc 直接编译到可执行文件
gcc main.c -o main
程序正常输出
3、分步编译
上步中一步到位的编译指令其实是拆分为以下四步:
- 预处理
- 编译为汇编代码
- 汇编
- 链接生成可执行文件
gcc -E main.c -o main.i
gcc -S main.i -o main.s
gcc -c main.s -o main.o
gcc main.o -o main
这四步分别生成了main.i文件、main.s文件、main.o文件,最后生成可执行文件main
分步编译后,程序可以正常运行
2.2 比较hello.asm与C代码生成的可执行文件
**1、首先安装 nasm **
apt-get install nasm
使用nasm -version查看完成安装情况
**2、编写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 ; 调用内核功能
**3、生成后运行可执行文件hello **
输入以下指令令生成hello.o文件,并且生成可执行文件hello
nasm -f elf64 hello.asm
ld -s -o hello hello.o
成功生成hello.o文件和可执行文件hello,接下来运行并查看文件大小
再使用C语言写一个“hello,world!”文件,用gcc编译生成可执行文件,用size查看大小
可以看到,使用nasm编译的hello.asm文件大小跟用gcc编译的main.c文件大小相比,大小小了非常多
三、了解实际程序是如何借助第三方库函数完成代码设计
3.1 Linux 中终端程序最常用的光标库(curses)
函数 | 功能 |
---|---|
int addch(const chtype char_to_add); | 当前位置添加字符 |
int delch(void) | 删除光标左边的字符 |
chtype inch(void); | 返回光标位置字符 |
int move(int new_y, int new_x); | 移动stdcsr的光标位置 |
void getyx(WINDOW *win,int y,int x); | 得到目前游标的位置. (请注意! 是 y,x 而不是&y,&x ) |
int mvwin(WINDOW *win, int new_y, int new_x); | 移动窗口 |
3.2 体验远古时代的 BBS
在 控制面板 -> 程序 -> 启用或关闭Windows功能,点选“Talnet 客户端”和“适用于 Linux 的 Windows 子系统”,点击 确定 ,根据指示重新启动。
重新启动后,进入cmd键入如下指令,即可进入远古论坛。
telnet bbs.newsmth.net
用guest用户登入后,里面有许多不易展示的内容,这里就不展示了[笑哭]
3.3 在Ubuntu中用 sudo apt-get install libncurses5-dev 安装curses库,查看库文件都被安装到哪些目录中
安装libncurses5-dev
apt-get install libncurses5-dev
curses头文件所在目录为/usr/include
3.4 Ubuntu 环境下C语言编译实现贪吃蛇游戏
贪吃蛇的程序代码来源于网络
//mysnake1.0.c
//编译命令:cc mysnake1.0.c -lcurses -o mysnake1.0
//用方向键控制蛇的方向
#include <stdio.h>
#include <stdlib.h>
#include <curses.h>
#include <signal.h>
#include <sys/time.h>
#define NUM 60
struct direct //用来表示方向的
{
int cx;
int cy;
};
typedef struct node //链表的结点
{
int cx;
int cy;
struct node *back;
struct node *next;
}node;
void initGame(); //初始化游戏
int setTicker(int); //设置计时器
void show(); //显示整个画面
void showInformation(); //显示游戏信息(前两行)
void showSnake(); //显示蛇的身体
void getOrder(); //从键盘中获取命令
void over(int i); //完成游戏结束后的提示信息
void creatLink(); //(带头尾结点)双向链表以及它的操作
void insertNode(int x, int y);
void deleteNode();
void deleteLink();
int ch; //输入的命令
int hour, minute, second; //时分秒
int length, tTime, level; //(蛇的)长度,计时器,(游戏)等级
struct direct dir, food; //蛇的前进方向,食物的位置
node *head, *tail; //链表的头尾结点
int main()
{
initscr();
initGame();
signal(SIGALRM, show);
getOrder();
endwin();
return 0;
}
void initGame()
{
cbreak(); //把终端的CBREAK模式打开
noecho(); //关闭回显
curs_set(0); //把光标置为不可见
keypad(stdscr, true); //使用用户终端的键盘上的小键盘
srand(time(0)); //设置随机数种子
//初始化各项数据
hour = minute = second = tTime = 0;
length = 1;
dir.cx = 1;
dir.cy = 0;
ch = 'A';
food.cx = rand() % COLS;
food.cy = rand() % (LINES-2) + 2;
creatLink();
setTicker(20);
}
//设置计时器(这个函数是书本上的例子,有改动)
int setTicker(int n_msecs)
{
struct itimerval new_timeset;
long n_sec, n_usecs;
n_sec = n_msecs / 1000 ;
n_usecs = ( n_msecs % 1000 ) * 1000L ;
new_timeset.it_interval.tv_sec = n_sec;
new_timeset.it_interval.tv_usec = n_usecs;
n_msecs = 1;
n_sec = n_msecs / 1000 ;
n_usecs = ( n_msecs % 1000 ) * 1000L ;
new_timeset.it_value.tv_sec = n_sec ;
new_timeset.it_value.tv_usec = n_usecs ;
return setitimer(ITIMER_REAL, &new_timeset, NULL);
}
void showInformation()
{
tTime++;
if(tTime >= 1000000) //
tTime = 0;
if(1 != tTime % 50)
return;
move(0, 3);
//显示时间
printw("time: %d:%d:%d %c", hour, minute, second);
second++;
if(second > NUM)
{
second = 0;
minute++;
}
if(minute > NUM)
{
minute = 0;
hour++;
}
//显示长度,等级
move(1, 0);
int i;
for(i=0;i<COLS;i++)
addstr("-");
move(0, COLS/2-5);
printw("length: %d", length);
move(0, COLS-10);
level = length / 3 + 1;
printw("level: %d", level);
}
//蛇的表示是用一个带头尾结点的双向链表来表示的,
//蛇的每一次前进,都是在链表的头部增加一个节点,在尾部删除一个节点
//如果蛇吃了一个食物,那就不用删除节点了
void showSnake()
{
if(1 != tTime % (30-level))
return;
//判断蛇的长度有没有改变
bool lenChange = false;
//显示食物
move(food.cy, food.cx);
printw("@");
//如果蛇碰到墙,则游戏结束
if((COLS-1==head->next->cx && 1==dir.cx)
|| (0==head->next->cx && -1==dir.cx)
|| (LINES-1==head->next->cy && 1==dir.cy)
|| (2==head->next->cy && -1==dir.cy))
{
over(1);
return;
}
//如果蛇头砬到自己的身体,则游戏结束
if('*' == mvinch(head->next->cy+dir.cy, head->next->cx+dir.cx) )
{
over(2);
return;
}
insertNode(head->next->cx+dir.cx, head->next->cy+dir.cy);
//蛇吃了一个“食物”
if(head->next->cx==food.cx && head->next->cy==food.cy)
{
lenChange = true;
length++;
//恭喜你,通关了
if(length >= 50)
{
over(3);
return;
}
//重新设置食物的位置
food.cx = rand() % COLS;
food.cy = rand() % (LINES-2) + 2;
}
if(!lenChange)
{
move(tail->back->cy, tail->back->cx);
printw(" ");
deleteNode();
}
move(head->next->cy, head->next->cx);
printw("*");
}
void show()
{
signal(SIGALRM, show); //设置中断信号
showInformation();
showSnake();
refresh(); //刷新真实屏幕
}
void getOrder()
{
//建立一个死循环,来读取来自键盘的命令
while(1)
{
ch = getch();
if(KEY_LEFT == ch)
{
dir.cx = -1;
dir.cy = 0;
}
else if(KEY_UP == ch)
{
dir.cx = 0;
dir.cy = -1;
}
else if(KEY_RIGHT == ch)
{
dir.cx = 1;
dir.cy = 0;
}
else if(KEY_DOWN == ch)
{
dir.cx = 0;
dir.cy = 1;
}
setTicker(20);
}
}
void over(int i)
{
//显示结束原因
move(0, 0);
int j;
for(j=0;j<COLS;j++)
addstr(" ");
move(0, 2);
if(1 == i)
addstr("Crash the wall. Game over");
else if(2 == i)
addstr("Crash itself. Game over");
else if(3 == i)
addstr("Mission Complete");
setTicker(0); //关闭计时器
deleteLink(); //释放链表的空间
}
//创建一个双向链表
void creatLink()
{
node *temp = (node *)malloc( sizeof(node) );
head = (node *)malloc( sizeof(node) );
tail = (node *)malloc( sizeof(node) );
temp->cx = 5;
temp->cy = 10;
head->back = tail->next = NULL;
head->next = temp;
temp->next = tail;
tail->back = temp;
temp->back = head;
}
//在链表的头部(非头结点)插入一个结点
void insertNode(int x, int y)
{
node *temp = (node *)malloc( sizeof(node) );
temp->cx = x;
temp->cy = y;
temp->next = head->next;
head->next = temp;
temp->back = head;
temp->next->back = temp;
}
//删除链表的(非尾结点的)最后一个结点
void deleteNode()
{
node *temp = tail->back;
node *bTemp = temp->back;
bTemp->next = tail;
tail->back = bTemp;
temp->next = temp->back = NULL;
free(temp);
temp = NULL;
}
//删除整个链表
void deleteLink()
{
while(head->next != tail)
deleteNode();
head->next = tail->back = NULL;
free(head);
free(tail);
}
使用如下指令编译生成可执行文件
gcc tcs.c -lcurses -o tcs
运行可执行文件