编译器背后的故事

本文详细介绍了如何使用GCC生成静态库(.a)和动态库(.so),并对比了两者生成可执行文件的大小。通过实际例子展示了程序借助第三方库,如curses库完成代码设计的过程,包括预处理、编译、汇编和链接的步骤。同时,文章提及了GCC命令行选项的含义,以及如何使用curses库的关键函数。
摘要由CSDN通过智能技术生成

一.用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);
}

然后运行即可。

总结

这个过程中了解了程序是怎么借助第三方库文件完成设计的,同时知道了编码命令也是经过了几个过程的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值