前言
这篇文章是描述GCC编译器的应用,静态库和动态库文件的应用和使用,以及GCC编译器工具集中各种软件的用途,还有关于如何借助第三方库函数完成代码设计。
一、可执行程序的组装,静态库.a和动态库.so的生成与使用
1.准备工作
先创建一个目录,然后在此目录下进行编程
创建四个文件并用gedit 对其进行编译
touch main1.c
touch sub1.c
touch sub2.c
touch sub1.h
gedit main1.c
gedit sub1.c
gedit sub1.h
gedit sub2.c
代码:
main1.c
#include<stdio.h>
#include"sub1.h"
void main()
{
int a=8,b=6;
float c,w;
c=x2x(a,b);
w=x2y(a,b);
printf("%f\n",c);
printf("%f\n",w);
}
sub1.c
float x2x(int a,int b)
{
float s;
s=a+b;
return s;
}
sub1.h
#ifndef SUB1_H
#define SUB1_H
float x2x(int a,int b);
float x2y(int a,int b);
#endif
sub2.c
float x2y(int a,int b)
{
float w;
w=a-b;
return w;
}
2.GCC创建静态库
2.1.生成目标文件
2.3.使用静态库文件,创建可执行程序
3.生成动态库文件和其使用
3.1.生成目标文件
3.2.共享动态库文件
3.3.使用动态库文件,创建可执行程序
出现错误,查看链接情况
发现没有对应的.so文件
这是linux自身系统设定的相应的设置的原因,就是只在/lib and /usr/lib下搜索对应动态库文件,故需要将对应.so文件拷贝到对应路径
结果去下:
3.4.对比二者生成的可执行文件大小
二、GCC编译工具集中各种软件的用途,ELF文件格式,汇编语言格式
1.GCC常用命令的应用
1.1.准备工作
创建并编辑文件test.c
代码:
#include<stdio.h>
int main()
{
printf("Hello World");
return 0;
}
1.2.预处理
这里是将test.c文件预处理为test.i文件
1.3.编译
将.i文件编译生成汇编程序.s,
汇编结果:
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $.LC0, %edi
movl $0, %eax
call printf
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
1.4.链接
动态链接
静态链接:
2.ELF文件
2.1.ELF 文件包含下面几个段
(1) .text:已编译程序的指令代码段
(2) .rodata:ro 代表 read only,即只读数据(譬如常数 const)
(3) .data:已初始化的 C 程序全局变量和静态局部变量
(4) .bss:未初始化的 C 程序全局变量和静态局部变量
(5) .debug:调试符号表,调试器用此段的信息帮助调试
查看各个section的信息:
2.2.反汇编
由于ELF文件无法被当作普通文本打开,如果希望查看一个ELF文件包含的指令和函数,需要用到反汇编
使用objdump -S将其反汇编并且将C语言源代码混合显示出来;
3.nasm编译器,汇编代码
3.1.安装nasm
在网上下载一个安装包,把它复制到虚拟机的文件夹中,然后在虚拟机里解压,在终端输入以下代码:
cd 复制的那个文件夹
tar zxvf (nasm-2.14rc16.tar.gz )括号内为你所下载的nasm的版本。
查看是否安装成功
nasm -version
3.2.进行一个汇编实例
编译汇编hello.asm文件;生成目标文件
用ld链接器把hello.o生成可执行程序hello。./hello查看运行结果,如下图。
查看文件大小
三.如何借助第三方库函数完成代码设计
1.courses的主要函数功能和几个基本函数
1.1.以initscr()开始,以endwin();结束
WINDOW *initscr(void); //初始化
int endwin(void); // 退出curses, 返回OK/ERR
1.2.输出到屏幕
int addch(const chtype ch); //当前位置add ch
int addchstr(chtype *const str);//当前位置add str
int printw(char *format,…); //类printf,格式化输出
int refresh(void);//刷新物理屏,更改逻辑屏,必须刷新才能在物理屏上面显示
int box(WINDOW *ptr, chtype v_ch, chtype h_ch); //围绕ptr指定窗口画方框,垂直/水平方向字符v_ch/h_ch
int insch(chtype ch); //插入ch,右移
int insertln(void); //插入空白行,下移
int delch(void); //删除光标位置字符,左移
int deleteln(void) //删除当前行,上移
int beep(void); //发声
int flash(void); //闪屏
1.3.从屏幕读取
chtype inch(void); //读取光标位置字符
int instr(char *string); //读取字符串到str,到NUL,不总是被支持
int innstr(char *str, int n_of_str); //读取n个字符到str,或NUL,不总是被支持
(4).清除屏幕
int erase(void); //清屏,以空白字符填充
int clear(void); //清屏
int clrtobot(void); //清至屏幕结尾
int clr
2.感受远古时代BBS
在window10中打开“控制面板”–>“程序”—>“启用或关闭Windows功能”,启用 “telnet client” 和"适用于Linux的Windows子系统"。 然后打开一个cmd命令行窗口,命令行输入 telnet bbs.newsmth.net,以游客身份体验一下即将绝迹的远古时代的 BBS 。
3.安装corses
输入下图中的第一行的程序:
头文件和库文件安装到以下目录:
4.Linux下C语言编译实现贪吃蛇游戏
事先创建一个snake目录
创建一个可执行文件snake1
执行snake1
结果如图:
四.总结
学习了动态库和静态库文件的建立和使用,用静态和动态库链接生成可执行文件,ELF文件的格式还有各种GCC命令的使用,汇编语言的格式,nasm的安装和使用,体验了即将绝迹的远古时代的BBS。安装curses库,最后实现了一个贪吃蛇小游戏。
贪吃蛇的代码:
#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);
}