gcc的进阶用法与以及第三方代码的了解与使用

1.gcc创建.a 静态库和 .so 动态库

创建三个例子程序,以及静态库和动态库的创建

1.编辑生成例子程序 hello.h、hello.c 和 main.c
使用vim编译器创建上述三个文件。
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;
}

2.再用gcc将hello.c生成.o文件,并用ls查看在test2目录下是否创建成功。
在这里插入图片描述
3.由.o文件创建静态库

#ar -crv libmyhello.a hello.o

再用ls查看
在这里插入图片描述
4.在程序中使用静态库
先用gcc生成main.o

gcc -c main.c

再生成可执行文件

gcc -o hello main.o libmyhello.a

在这里插入图片描述
5.由.o文件创建动态库
动态库与静态库的创建方式相似,只不过静态后缀名为.a,动态为.so。在test2目录下创建动态库

gcc -shared -fPIC -o libmyhello.so hello.o

再次ls查看目录下libmyhello.so是否生成
在这里插入图片描述
然后再生成hello的可执行文件

gcc -c main.c
gcc -o hello main.o libmyhello.so

再将动态库libmyhello.so复制到/usr/lib目录下。注意,
当前操作可能权限不够,可以通过sudo chmod -R 777 来更改权限,或者单次权限用sudo。然后再次运行./hello程序,输出正常。

sudo mv libmyhello.so /usr/lib

在这里插入图片描述

将自建的三个函数用静态和动态库进行链接

创建三个函数main.c sub1.c sub2.c。
main.c

#include<stdio.h>
float x2x(float a, float b);
float x2y(float c);
float main() {
float a;float b;
scanf("%f%f",&a,&b);
printf("a=%f,b=%f\n",a,b);	
printf("(a*b)^2=%f\n",x2y(x2x(a,b)));
}

sub1.c

float x2x(float a, float b) 
{
	float t=a*b;
return t;
}

sub2.c

float x2y(float c)
{
	float t=c*c;
return t;
}

将上创建的三个函数分别创建他们的.o文件
在这里插入图片描述

将主函数与静态库链接

首先用sub1和sub2函数生成静态库。这里我们创建一个名为new的静态库,然后包含sub1和sub2的函数。

ar -crv libmynew.a sub1.o sub2.o

在用gcc把main函数和静态库new链接起来,这里创建一个名为new的可执行文件

gcc -o new main.o libmynew.a

然后运行可执行文件new
在这里插入图片描述

将主函数动态库链接

输入以下命令生成包含sub1和sub2的动态库。

gcc -shared -fPIC -o libmynew.so sub1.o sub2.o

再将上述生成的libmynew.so动态库复制到/usr/lib下

sudo cp libmynew.so /usr/lib

在这里插入图片描述
再生成由动态库和main组成的可执行文件new1
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

结论:动态库创建的程序比静态库略小一点。

2.gcc的“再使用”

创建测试目录test0,并生成一个简单的程序hello.c

#include <stdio.h> 
int main(void) 
{ 
printf("Hello World! \n");
 return 0; 
}

预处理

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

gcc -E hello.c -o hello.i

生成hello.i文件

编译

gcc -S hello.i -o hello.s

由上述的hello.i生成hello.s文件

汇编

gcc -c hello.s -o hello.o

由上述hello.s生成hello.o可执行和链接文件。

链接

gcc hello.c -o hello

然后生成hello的可执行文件。

3.分析 ELF 文件

ELF文件的查看

使用 readelf -S 查看其各个 section 的信息

readelf -S hello

在这里插入图片描述
但是一个ELF文件是无法直接打开的,如果想查看里面的内容

objdump -D hello

在这里插入图片描述

使用 objdump -S 将其反汇编并且将其 C 语言源代码混合显示

gcc -o hello -g hello.c
objdump -S hello

在这里插入图片描述

nasm汇编编译器的使用

首先下载nasm汇编编译器

sudo apt-get install nasm

将test.asm文件生成test.o程序

nasm -f elf64 test.asm

再将test.o生成可执行程序

ld -s -o test test.o

在这里插入图片描述

4.第三方代码的学习

Linux 系统中终端程序最常用的光标库curses的主要函数功能

 1 int flash(void);   //闪烁
 2 int addchstr(chtype *const string_to_add); //当前位置添加字符(串)
 3 int printw(char *format, ...);   //类似与printf
 4 int refresh(void);    //刷新物理屏幕
 5 int box(WINDOW *win_ptr, chtype vertical, chtype horizontal);   //围绕窗口绘制方框
 6 int insch(chtype char_to_insert);   //插入一个字符(已有字符后移)
 7 int insertln(void);   //插入空白行
 8 int deleteln(void);   //删除字符和空白行
 9 int beep(void);   //终端响铃
 10 int erase(void);   //在屏幕的每个位置写上空白字符
 11 int clear(void);    //使用一个终端命令来清除整个屏幕,内部调用了clearok来执行清屏操作,(在下次调用refresh时可以重现屏幕原文)
 12 int clrtobot(void);   //清除光标位置到屏幕结尾的内容
 13 int attroff(chtype attribute);   //启用或关闭某属性
 14 int standend(void);          //这两个表示更加通用的强调模式,通常映射为反白显示
 15 int cbreak();   //设置cbreak模式,字符一键入,直接传给程序
 16 int raw();    //关闭特殊字符处理
 17 int noraw();   //同时回复默认模式和特殊字符处

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

在 win10 系统中,“控制面板”–>“程序”—>“启用或关闭Windows功能”,启用 “telnet client” 和"适用于Linux的Windows子系统"
在这里插入图片描述
然后打开一个cmd命令行窗口,命令行输入 telnet bbs.newsmth.net
在这里插入图片描述
在这里插入图片描述

在Ubuntu中安装curses库

sudo apt-get install libncurses5-dev

在这里插入图片描述
可以看出curses的头函数在/usr/include目录下
在这里插入图片描述
库文件输入命令

locate curses

在这里插入图片描述

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

#include <stdio.h>
#include <stdlib.h>
#include <curses.h>
#include <signal.h>
#include <sys/time.h>
#include <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);
}

编辑命令生成贪吃蛇游戏

cc mysnake1.0.c -lcurses -o mysnake1.0

在这里插入图片描述

总结

1.学习到了用gcc把简单函数去链接静态库和动态库,知道了静态库和动态库的一些工作原理,静态库在可执行文件生成好后就不会再调用了,动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入,动所以态库会一直伴随着程序结束。
2.熟悉了gcc在从.c文件到可执行文件过程中每一步的作用,包括预处理,编译,汇编,链接。
3.了解一些分析ELF文件的知识,根据实验步骤去验证了gcc在编译过程的一些文件。
4.去查询了一些在Linux 系统中终端程序最常用的光标库curses的主要函数功能,去浏览了最远古的BBS。然后安装了curses库,下载并运行了贪吃蛇以及俄罗斯方块一类的游戏。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值