一.用gcc生成.a静态库和.so动态库
1.创建一个作业目录tset1
2.然后用vi文本编辑器编辑生成所需要的3个文件
程序 hello.h 内容如下:
#ifndef HELLO_H
#define HELLO_H
void hello(const char *name);
#endif//HELLO_H
程序 hello.c 内容如下:
#include <stdio.h>
#include "hello.h"
void hello(const char *name)
{
printf("Hello %s!\n", name);
}
程序 main.c 内容如下:
#include"hello.h"
int main()
{
hello("everyone");
return 0;
}
3.将 hello.c 编译成.o 文件
gcc -c hello.c # 编译成 .o文件
并用ls命令查看是否生成文件
4.由.o文件创建静态库
ar -crv libmyhello.a hello.o # 生成静态库
5.在程序中使用
#gcc main.c libmyhello.a -o hello
6.由 .o文件创建动态库
gcc -shared -fPIC -o libmyhello.so hello.o
用ls命令查看动态库文件是否生成:
7.使用动态库
gcc main.c libmyhello.so -o hello
二、动态库和静态库生成可执行文件大小的对比
1.创建一个 test2文件夹,分别创建main.c、main1.c、main2.c三个文件并用gedit编辑。
main.c
#include"main1.c"
#include"main2.c"
#include<stdio.h>
int main()
{
float a=2;
float b=5;
printf("a+b=%f\n",x(a,b));
printf("a*b=%f\n",y(a,b));
return 0;
}
main1.c
#ifndef SUB1_H
#define SUB1_H
float x2x(int a, int b)
{
return (a+b);
}
main2.c
#ifndef SUB2_H
#define SUB2_H
float x2y(int a, int b){
return(a*b);
}
(1)用静态库文件进行链接,生成可执行文件
然后用gcc命令将三个文件编译为.o的目标文件
gcc -c main.c main1.c main2.c
之后将main1.c和main2.c目标文件用 ar工具生成1个 .a 静态库文件
运行main文件,得到了我们想要的结果
(2)用动态库文件进行链接,生成可执行文件
将main1.c、main2.c目标文件用 ar工具生成1个 .so 动态库文件, 然后用 gcc将 main函数的目标文件与此动态库文件进行链接,生成最终的可执行程序
(3)比较大小
查看大小之后发现静态库比动态库大
三、GCC编译工具中各软件用途及汇编语言
1.gcc命令
gcc命令:GCC 原名为 GNU C 语言编译器(GNU C Compiler),因为它原本只能处理C语言,GCC 很快地扩展,可处理 C++,后来又扩展为能够支持更多编程语言,如Fortran、Pascal、Objective-C、Java、Ada、
Go以及各类处理器架构上的汇编语言等,所以改名GNU编译器套件(GNU Compiler Collection)
(1)gcc命令下各选项的含义
-E:仅作预处理,不进行编译、汇编和链接
-S:仅编译到汇编语言,不进行汇编和链接
-c:编译、汇编到目标代码(也就是计算机可识别的二进制)
-o:执行命令后文件的命名
-g:生成调试信息
-w:不生成任何警告
-Wall:生成所有的警告
2.程序的编译过程
a. 预编译(将源文件 hello.c 文件预处理生成 hello.i)
b. 编译(将预处理生成的 hello.i 文件编译生成汇编程序 hello.s)
c. 汇编(将编译生成的 hello.s 文件汇编生成目标文件 hello.o)
d. 链接(分为静态链接和动态链接,生成可执行文件)
e. 用 size 查看文件大小,ldd链接了那些动态库
四、了解实际程序是如何借助第三方库函数完成代码设计
1.光标库(curses)的主要函数功能
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 将做清除萤幕的工作。
2.以游客身份体验一下即将绝迹的远古时代的 BBS
3.安装curses库。头文件被安装在usr/include里,库文件被安装在usr/lib中。
4 .gcc编译一个小游戏《贪吃蛇》
将Linux环境下C语言编译实现贪吃蛇游戏的代码复制到tcs.c中
#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);
}
然后运行即可。
总结
这个过程中了解了程序是怎么借助第三方库文件完成设计的,同时知道了编码命令也是经过了几个过程的。