編譯器背後的故事(入門練習)
編譯器背後的故事(入門練習)
一、使用gcc生成.a靜態庫與.so動態庫檔案
- 建立3個程式以作為本次實驗例子
a) Practice.h
b) Practice.c#ifndef HELLO_H #define HELLO_H void hello(const char *name); #endif
b) Main.c#include <stdio.h> void hello(const char *name) { printf("MyNameIs %s!\n", name); }
#include "Practice.h" int main() { hello("OctoNebula47"); return 0; }
- 第 2 步:將 Practice.c 編譯成.o 檔案
在當前目錄下開啟終端並輸入如下指令
現在可以看到生成了一個Practice.o檔案gcc -c Practice.c
- 由.o 檔案建立.a靜態庫
在終端中輸入如下指令
得到以下檔案ar -crv libmyPractice.a Practice.o
- 在Main程式中使用靜態庫,生成程式Practice
得到以下檔案gcc Main.c libmyPractice.a -o Practice
- 由.o 檔案建立.so動態庫檔案
目前資料夾中含有如下檔案gcc -shared -fPIC -o libmyPractice.so Practice.o
- 在程式中使用動態庫
如果終端中顯示如下則沒有出錯,即可進行下一步gcc Main.c libmyPractice.so -o Practice
此時直接執行Practice會出現以下錯誤
此時只需要將libmyPractice.so檔案複製到usr/lib目錄下即可
然後執行Practice即可sudo cp libmyPractice.so /usr/lib
./Practice
二、靜態庫檔案使用
-
建立3個程式以作為本次實驗例子
main程式功能:呼叫x2x程式與x2y程式
x2x程式功能:z1 = x * x
x2y程式功能:z2 = x * ya)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); }
-
分別將這三個檔案使用gcc編譯成.o檔案
gcc -c main.c
gcc -c x2x.c
gcc -c x2y.c
得到以下檔案
-
將x2x.c和x2y.c目標檔案用 ar工具生成1個 .a 靜態庫檔案
ar -crv libmy2xy.a x2x.o x2y.o
得到以下檔案
-
使用gcc將main函式的目標檔案與此靜態庫檔案進行連結
gcc main.c libmy2xy.a -o amain
得到以下最終可執行程式
-
執行main程式
./amain
amain檔案大小為16744B
三、動態庫檔案使用
該實驗內容緊接實驗二「二、靜態庫檔案使用」內容
-
將x2x、x2y目標檔案用ar工具生成1個 .so 動態庫檔案
ar -crv libmy2xy.so x2x.o x2y.o
得到以下檔案
-
用gcc將main函式的目標檔案與此動態庫檔案進行連結
gcc main.c libmy2xy.so -o somain
得到以下可執行程式
-
執行main程式
./somain
可見somain檔案大小為16744B
通過比較動態庫與靜態庫生成的可執行檔案大小相同,均為16744B。
但應該不一樣大才對,我目前也沒有找到具體是哪裡出錯了
四、Linux GCC常用命令
-
完成該實驗需要準備以下一個main.c檔案:
#include <stdio.h> int main(void) { printf("My name is OctoNebula47\n"); return 0; }
-
一步到位編譯
一般情況下,我們只是用一行指令即可將一個.c檔案編譯並生成一個可執行檔案gcc main.c -o main
-
分步編譯
上一個步驟中一步到位的編譯指令其實分為以下四步進行:
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程式碼生成的可執行檔案
-
在Ubuntu下安裝nasm
sudo apt install nasm
如果出現錯誤,無法下載的情況,可以進入nasm官網進行下載
-
在Ubuntu瀏覽器中進入nasm官網
-
選擇最新版本,並下載.tar.gz格式的檔案
kx上網可體驗滿速下載╰( ̄ω ̄o)
-
下載好後解壓檔案,進入解壓目錄內開啟終端,分步輸入以下指令
./configure
make
sudo make install
nasm -version
若出現nasm版本資訊則表示已經成功安裝nasm
-
-
新建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 ; 呼叫核心功能
-
生成並執行可執行檔案hello
在當前目錄下開啟終端,並輸入以下指令生成hello.o檔案nasm -f elf64 hello.asm
生成可執行檔案hello
ld -s -o hello hello.o
執行hello檔案並記錄檔案大小
-
C程式碼的編譯生成執行程式並記錄檔案大小
hello.c程式碼如下#include<stdio.h> int main() { printf("hello, world!\n"); return 0; }
生成執行.out檔案,並記錄檔案大小
-
比較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
-
進入WIN10系統控制皮膚,選擇「程式和功能」
-
點選「啟用或關閉 Windows 功能」
-
勾選「Telnet Client」與「適用於 Linux 的 Windows 子系統」
-
開啟 cmd 控制檯,輸入以下指令
telnet bbs.newsmth.net
-
開始體驗
emmmmm
裡面有好多奇奇怪怪的內容
看了之後奇怪的知識增加了
懂的都懂
八、在Ubuntu中用 sudo apt-get install libncurses5-dev 安裝curses庫
在終端中輸入以下指令
sudo apt-get install libncurses5-dev
curses標頭檔案所在目錄為/usr/include
九、Ubuntu 環境下C語言編譯實現貪吃蛇遊戲
-
建立 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); }
-
在終端中使用以下指令生成可執行檔案
gcc tcs.c -lcurses -o tcs
-
執行檔案
相關文章
- GCC編譯器背後的故事GC編譯
- 嵌入式—編譯器背後的故事編譯
- Rust 編譯器入門Rust編譯
- JIT 編譯器快速入門編譯
- 交叉編譯入門編譯
- Redis持久化背後的故事Redis持久化
- c#入門-編譯的概念C#編譯
- dyld背後的故事&原始碼分析原始碼
- 愛回收IPO背後的新老故事
- RestCloud ETL 社群版背後的故事RESTCloud
- 笨辦法學C 練習1:啟用編譯器編譯
- 郭超:阿里雲Cassandra背後的故事阿里
- JavascriptAST編譯器的研究學習JavaScriptAST編譯
- 編譯器後端總結編譯後端
- CAD入門級練習題
- 蘋果自動駕駛背後的故事蘋果自動駕駛
- 安能物流 All in TiDB 背後的故事與成果TiDB
- 請求 www.baidu.com 背後的故事AI
- 誰來背鍋?自動駕駛車禍背後的故事自動駕駛
- 更好的 java 重試框架 sisyphus 背後的故事Java框架
- 【前端軼事】Chrome 小恐龍背後的故事前端Chrome
- 聊聊百度搜尋背後的故事
- 開源筆記軟體 Joplin 背後的故事筆記
- 4399《胡偵探傳說》系列背後的故事
- sql查詢入門練習題SQL
- xjbz京東電器攜肖戰獻上賀歲大片《後背》,講述冠軍背後的守護故事
- [編譯] 10、kconfig 入門指導教程編譯
- 騰訊與Github的魔幻會面背後的故事…Github
- JAVA入門第三季——最後練習題撲克牌Java
- [譯] Service workers:PWA背後的英雄
- [譯] ? styled-components 背後的魔法
- 編譯原理實戰入門:用 JavaScript 寫一個簡單的四則運算編譯器(修訂版)編譯原理JavaScript
- 《碼出高效:Java開發手冊》背後的故事Java
- CVE-2016-1779技術分析及其背後的故事
- 《百英雄傳》眾籌450萬美元背後的故事
- What CANN Can?一輛小車背後的智慧故事
- 我們的20年 | 講述雲安全背後的故事
- 滑鼠打字的背後,隱藏著一個感人的故事