編譯器背後的故事(入門練習)

OctoNebula47發表於2020-10-16

一、使用gcc生成.a靜態庫與.so動態庫檔案

  1. 建立3個程式以作為本次實驗例子
    在這裡插入圖片描述
    a) Practice.h
    #ifndef HELLO_H
    #define HELLO_H
    void hello(const char *name);
    #endif
    
    b) Practice.c
    #include <stdio.h> 
    void hello(const char *name)
    {
    	printf("MyNameIs %s!\n", name); 
    }
    
    b) Main.c
    #include "Practice.h" 
    int main()
    {
    	hello("OctoNebula47"); 
    	return 0;
    }
    
  2. 第 2 步:將 Practice.c 編譯成.o 檔案
    在當前目錄下開啟終端並輸入如下指令
    gcc -c Practice.c
    
    現在可以看到生成了一個Practice.o檔案
    在這裡插入圖片描述
  3. 由.o 檔案建立.a靜態庫
    在終端中輸入如下指令
    ar -crv libmyPractice.a Practice.o
    
    得到以下檔案
    在這裡插入圖片描述
  4. 在Main程式中使用靜態庫,生成程式Practice
    gcc Main.c libmyPractice.a -o Practice
    
    得到以下檔案
    在這裡插入圖片描述
  5. 由.o 檔案建立.so動態庫檔案
    gcc -shared -fPIC -o libmyPractice.so Practice.o
    
    目前資料夾中含有如下檔案
    在這裡插入圖片描述
  6. 在程式中使用動態庫
    gcc Main.c libmyPractice.so -o Practice
    
    如果終端中顯示如下則沒有出錯,即可進行下一步
    在這裡插入圖片描述
    此時直接執行Practice會出現以下錯誤在這裡插入圖片描述
    此時只需要將libmyPractice.so檔案複製到usr/lib目錄下即可
    在這裡插入圖片描述
    sudo cp libmyPractice.so /usr/lib
    
    然後執行Practice即可
    ./Practice
    
    在這裡插入圖片描述

二、靜態庫檔案使用

  1. 建立3個程式以作為本次實驗例子
    在這裡插入圖片描述

    main程式功能:呼叫x2x程式與x2y程式
    x2x程式功能:z1 = x * x
    x2y程式功能:z2 = x * y

    a)main.c

    #include"x2x.c"
    #include"x2y.c"
    #include<stdio.h>
    int main()
    {
    	float x=2;
    	float y=5;
    	printf("z1=%f\n",x2x(x));
    	printf("z2=%f\n",x2y(x,y));
    	return 0;
    }
    

    b)x2x.c

    #include<stdio.h>
    float x2x(float x)
    {
    	float z1;
    	z1 = x * x;
    	return (z1);
    }
    

    c)x2y.c

    #include<stdio.h>
    float x2y(float x, float y)
    {
    	float z1;
    	z2 = x * y;
    	return (z2);
    }
    
  2. 分別將這三個檔案使用gcc編譯成.o檔案

    gcc -c main.c
    
    gcc -c x2x.c
    
    gcc -c x2y.c
    

    得到以下檔案
    在這裡插入圖片描述

  3. 將x2x.c和x2y.c目標檔案用 ar工具生成1個 .a 靜態庫檔案

    ar -crv libmy2xy.a x2x.o x2y.o
    

    得到以下檔案

    在這裡插入圖片描述

  4. 使用gcc將main函式的目標檔案與此靜態庫檔案進行連結

    gcc main.c libmy2xy.a -o amain
    

    得到以下最終可執行程式
    在這裡插入圖片描述

  5. 執行main程式

    ./amain
    

    在這裡插入圖片描述

amain檔案大小為16744B

三、動態庫檔案使用

該實驗內容緊接實驗二「二、靜態庫檔案使用」內容

  1. 將x2x、x2y目標檔案用ar工具生成1個 .so 動態庫檔案

    ar -crv libmy2xy.so x2x.o x2y.o
    

    得到以下檔案
    在這裡插入圖片描述

  2. 用gcc將main函式的目標檔案與此動態庫檔案進行連結

    gcc main.c libmy2xy.so -o somain
    

    得到以下可執行程式
    在這裡插入圖片描述

  3. 執行main程式

    ./somain
    

    在這裡插入圖片描述

可見somain檔案大小為16744B

通過比較動態庫與靜態庫生成的可執行檔案大小相同,均為16744B。
但應該不一樣大才對,我目前也沒有找到具體是哪裡出錯了

四、Linux GCC常用命令

  1. 完成該實驗需要準備以下一個main.c檔案:

    #include <stdio.h> 
    int main(void)
    { 
    	printf("My name is OctoNebula47\n"); 
    	return 0;
    }
    
  2. 一步到位編譯
    一般情況下,我們只是用一行指令即可將一個.c檔案編譯並生成一個可執行檔案

    gcc main.c -o main
    

    在這裡插入圖片描述

  3. 分步編譯
    上一個步驟中一步到位的編譯指令其實分為以下四步進行:
    a) 預處理
    b) 編譯為彙編程式碼
    c) 彙編
    d) 連結生成可執行檔案

    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
    

    注意在彙編時,指令為 -c 而不是 -C,此處的字母應該為小寫。

    在這四步中獲得以下檔案
    在這裡插入圖片描述
    執行結果如下:
    在這裡插入圖片描述

五、比較hello.asm與C程式碼生成的可執行檔案

  1. 在Ubuntu下安裝nasm

    sudo apt install nasm
    

    如果出現錯誤,無法下載的情況,可以進入nasm官網進行下載

    1. 在Ubuntu瀏覽器中進入nasm官網

    2. 選擇最新版本,並下載.tar.gz格式的檔案
      在這裡插入圖片描述

      kx上網可體驗滿速下載╰( ̄ω ̄o)

    3. 下載好後解壓檔案,進入解壓目錄內開啟終端,分步輸入以下指令

      ./configure
      
      make
      
      sudo make install
      
      nasm -version
      

      若出現nasm版本資訊則表示已經成功安裝nasm
      在這裡插入圖片描述

  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檔案

    nasm -f elf64 hello.asm
    

    生成可執行檔案hello

    ld -s -o hello hello.o
    

    在這裡插入圖片描述
    執行hello檔案並記錄檔案大小
    在這裡插入圖片描述

  4. C程式碼的編譯生成執行程式並記錄檔案大小
    hello.c程式碼如下

    #include<stdio.h>
    int main()
    {
    	printf("hello, world!\n");
    	return 0;
    }
    

    生成執行.out檔案,並記錄檔案大小
    在這裡插入圖片描述

  5. 比較hello與a.out檔案大小

    text	data	bss		dec		hex		filename
    34		14		0		48		30		hello
    1567	600		8		2175	87f		a.out
    

    差別如此明顯,這裡就不說明了,看看就好╮(╯-╰)╭

六、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);移動視窗

七、體驗遠古時代的 BBS

  1. 進入WIN10系統控制皮膚,選擇「程式和功能」
    在這裡插入圖片描述

  2. 點選「啟用或關閉 Windows 功能」
    在這裡插入圖片描述

  3. 勾選「Telnet Client」與「適用於 Linux 的 Windows 子系統」
    在這裡插入圖片描述

  4. 開啟 cmd 控制檯,輸入以下指令

    telnet bbs.newsmth.net
    
  5. 開始體驗
    在這裡插入圖片描述
    在這裡插入圖片描述

    emmmmm
    裡面有好多奇奇怪怪的內容
    看了之後奇怪的知識增加了
    懂的都懂

八、在Ubuntu中用 sudo apt-get install libncurses5-dev 安裝curses庫

在終端中輸入以下指令

sudo apt-get install libncurses5-dev

在這裡插入圖片描述
curses標頭檔案所在目錄為/usr/include
在這裡插入圖片描述

九、Ubuntu 環境下C語言編譯實現貪吃蛇遊戲

  1. 建立 GreedySnake.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);
    }
    
  2. 在終端中使用以下指令生成可執行檔案

    gcc tcs.c -lcurses -o tcs
    
  3. 執行檔案
    在這裡插入圖片描述

相關文章