編譯環境介紹
最常用的文書編輯器vi及vim
安裝vim vi和vim的最大不同在於vim支 援「多顏色」的編輯環境,因 此我們選用vim vi幾乎是所有Linux都提供的編 輯器,請同學自學 第一次執行vim系統會說沒有 這個軟體,請依照螢幕只是輸 入 sudo apt-get install vim
使用圖形化介面安裝vim Ubuntu內建Ubuntu software center,可以使用圖形化介面 安裝程式 標準的vim是純文字介面, Ubuntu software center允許我 們安裝圖形化介面的vim 你們喜歡圖形化介面或純文字 介面呢?
先使用容易上手的「圖形化」vim 輸入「a」這個字母代表我們 要開始編譯 輸入程式碼 將檔案存成~/sp/hello.c 儲存的方式是按下「ESC」然後 打w hello.c 這時候你可以看到你的程式碼的 關鍵字變色了
一些設定 選擇下列選項,讓vim協助我 們程式碼的排版,並列出行號 Toggle Line Numbering Toggle auto-indent Toggle C-indenting
使用vim的「快捷鍵」
為何要學習「快捷鍵」 學會快捷鍵以後,編輯程式碼的速度可以變得更快 如果你使用的vim沒有圖形化介面,那麼你只能使用「快捷鍵」
Vim的編輯模式 編輯模式 按下i,o,a進入編輯模式,ESC退回一般模式 一般模式 指令模式 按下「:」進入命令列模式,ESC退回一般模式
vim一般模式常用功能 瀏覽 搜尋 上下左右鍵,移動游標 ctr-f, ctr-b,向下或向上移動一頁 0或「home」移到該列的第一個字元,$或「end」移到該列最後一個字 元 G移到最後一列,gg移到第一列 搜尋 「/word」向下尋找word這個字 「?word」向上尋找word這個字 n繼續尋找
vim一般模式常用功能 刪除、複製、貼上 x, X:小x向後刪除一個字元,大X向前刪除一個字元 dd:刪除整個列 ndd:n是一個數字,例如20,20dd代表向下刪除20列 yy:複製游標所在的那一行 nyy:複製游標所在的底下n行 p:將複製的資料,於游標的下一行開始貼上 u:復原前一個動作 [ctr]+r:重做上一個動作
一般指令模式 i:從目前游標所在位置開始插入(輸入) 「ESC」:退出一般模式
指令模式 :w,寫入檔案 :w!,強制寫入檔案 :q,離開vim :q!,強制離開vim :wq,寫入檔案並離開
寫程式碼常用指令 Ctr-n:自動補上該字
使用圖形化編輯器
選擇哪一個圖形化編輯器呢 - Geany
安裝
Geany的對程式碼編輯的支援 自動完成功能 參數提示功能
Geany的編譯環境
gcc 與 gdb
gcc常用的編譯參數 gcc xxx.c gcc xxx.c -o exec gcc xxx.c -O3 gcc xxx.c –ansi xxx.c必需內含main function,編譯以後產生的執行檔檔名為a.out gcc xxx.c -o exec xxx.c必需內含main function,編譯以後產生的執行檔檔名為exec gcc xxx.c -O3 xxx.c必需內含main function,啟動最佳化,O後面可以為1, 2, 3 gcc xxx.c –ansi xxx.c必須使用標準的ANSI C撰寫,所有gcc的擴充語法都不可以使用 gcc xxx.c –g 產生的執行檔案可以用gdb除錯 gcc xxx.c -pg 產生的執行檔案可以用gprofile量測效能
一個簡單但錯誤的例子 #include <stdio.h> #include <stdlib.h> int main () { int *p; /*指標未給初始值*/ int ret; ret = scanf("%d", p); printf("ret = %d, %d", ret, *p); }
編譯並除錯 $gcc pointer.c –g –o pointer $gdb ./pointer shiwulo@ubuntu:~/sp$ gdb ./pointer /*一堆版權宣告訊息*/ (gdb)b main /*在main函數設定break point*/ Breakpoint 1 at 0x4005a5: file pointer.c, line 6. (gdb)r /*開始執行*/ Starting program: /home/shiwulo/sp/pointer Breakpoint 1, main () at pointer.c:6 6 scanf(“%d”, p); /*程式碼停在main函數的第一行*/ 請注意,紅色字體為提醒文字,並非Linux所輸出的文字
編譯並除錯 (gdb)n 1 /*你的執行檔pointer等著你輸入一個字*/ Program received signal SIGSEGV, Segmentation fault. 0x00007ffff7a6c742 in _IO_vfscanf_internal (s=<optimized out>, format=<optimized out>, argptr=argptr@entry=0x7fffffffde08, errp=errp@entry=0x0) at vfscanf.c:1857 1857 vfscanf.c: No such file or directory. /*gdb告訴你發生segmentation fault*/
編譯並除錯 (gdb)bt #0 0x00007ffff7a6c742 in…at vfscanf.c:1857 #1 0x00007ffff7a72ed9 in … at isoc99_scanf.c:37 #2 0x00000000004005bb in main () at pointer.c:6 /*bt可以看到目前的呼叫堆疊*/ /*從上述的訊息可以知道pointer.c的第6行呼叫isoc99_scanf.c,isoc99_scanf.c呼叫vfscanf.c,並且在vfsacnf.c的1857行發生錯誤,由於我們相信libC應該不會有bug,所以bug很有可能發生在pointer.c的第6行*/ 請注意…代表我刪除了一些沒用的訊息
編譯並除錯 vfscanf 目前在此函數 Isoc99_scanf (gdb) up #1 0x00007ffff7a72ed9 … at isoc99_scanf.c:37 37 isoc99_scanf.c: No such file or directory. #2 0x00000000004005bb in main () at pointer.c:6 6 scanf(“%d", p); (gdb) /*注意,還未執行up前,gdb在「呼叫堆疊」裡面是位於vfscanf,因此必須執行二次的up才會到pointer.c*/ (gdb) print p /*print可以印出某個變數的值*/ $1 = (int *) 0x0 /*到這裡我們可以確定這個程式碼是因為指標未給初始值造成錯誤*/ pointer 二次up後到此函數
編譯並除錯 常用的gdb功能 help 印出gdb的功能與解釋 commends定義簡短指令 b 設定breakpoint watch觀察某一個變數是否被修改 bt 看目前的呼叫堆疊 l 看目前的程式碼 s 單步執行(遇到函數會跳進去 追蹤) n 單步執行(遇到函數不會跳進 去追蹤) p 印出某個變數的值 r 開始執行 info 會印出許多有用的東西
gdb -tui ./pointer ┌──pointer.c────────────────────────────────────────────────────────────────┐ │4 int main () { │ │5 int *p; /*M-f~L~GM-fM-(~YM-f~\M-*M-gM-5M-&M-e~H~]M-│ │6 int ret; │ │7 ret = scanf("%d", p); │ │8 printf("ret = %d, %d", ret, *p); │ │9 } │ │10 │ │11 │ │12 │ │13 │ │14 │ │15 │ │16 │ └───────────────────────────────────────────────────────────────────────────┘ exec No process In: L?? PC: ?? For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>. Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from ./pointer...done. (gdb)
Code::Blocks(自己試試看,感覺不穩定)
Visual Studio Code https://code.visualstudio.com/download https://marketplace.visualstudio.com/items?itemName=ms- vscode.cpptools
File->Open Folder
Debug(在此步驟之前,記得先編譯)
Debug
設定(文字檔案,設定完記得儲存)
設定中斷點
開始執行
也是一個不錯的編輯器 (有自動填入功能)
Visual Studio Code未提供的功能
小結 必須熟悉gcc及gdb,因為Linux上的工具並無法完全替代掉這二 者 一定要熟悉vi及vim UNIX上一定會有vi UNIX與Windows不同,所有的設定檔都是「純文字」檔案,當系統崩潰 時你可以使用「single user mode」啟動,然後編輯設定檔案進行修復 可以選擇一個你最喜歡的GUI的環境,當平常寫程式用的工具
make:簡化常用的編譯指令
語法 target: source1.o source2.o… /*如果source1.o, source2.o其中有*/ gcc source1.o source2.o –o target /*一個比target還要新*/ /*就會執行gcc*/ clean: /*無預設條件*/ rm *.o
實例 pointer: pointer.c /*只有當pointer.c的時間比pointer還新時*/ gcc pointer.c -O3 -o pointer /*才會執行gcc指令*/ hello: hello.c gcc hello.c -O3 -o hello table: table.c gcc table.c -O3 -o table clean: /*只要執行clean就一定會執行remove*/ rm pointer rm hello rm table
更複雜一點點的例子 all: pointer hello table /*打make all時,只要xxx.c有任何變動,其*/ touch all /*對應的gcc會重新編譯該檔案*/ pointer: pointer.c gcc pointer.c -O3 -o pointer hello: hello.c gcc hello.c -O3 -o hello table: table.c gcc table.c -O3 -o table clean: rm pointer rm hello rm table
更複雜一點點的例子 all: pointer hello table /*打make all時,只要xxx.c有任何變動,其*/ touch all /*對應的gcc會重新編譯該檔案*/ pointer: pointer.c touch pointer /*www.cs.ccu.edu.tw/~shiwulo/course*/ hello: hello.c touch hello table: table.c touch table clean: rm pointer rm hello rm table
GNU C內建的效能衡量工具 gprofile
範例程式 如右圖,一個表格(table), 分別計算欄(col)的總和及列 (row)的總和 程式碼的程式碼如下頁所示
程式碼 將table初始化 列印結果 以row major方式計算 主函數,呼叫各個子函數 以col major方式計算 #define size 10000 long table[size][size]; long col[size]; long row[size]; void initTable() { int i, j; for (i=0; i< size; i++) for (j=0; j<size; j++) table[i][j]=random(); } void sumCol() { for (i=0; i<size; i++) col[j] = table[i][j]; void sumRow() { row[i] = table[i][j]; void printResult() { int i; printf(" RAW\tCol\n"); printf("%8ld\t%8ld\n", row[i], col[i]); int main() { printf("hello\n"); initTable(); sumRow(); sumCol(); printResult(); return 0; 將table初始化 列印結果 以row major方式計算 主函數,呼叫各個子函數 以col major方式計算
編譯的命令,及gprof命令 gcc table.c -pg -o table /*編譯,注意,要加入-pg*/ ./table /*執行,會自動產生gmon.out*/ gprof -b table gmon.out /*產生報表,如下頁所示*/
gprof產生的報表 主要產生 各個函數的執行時間 call graph(呼叫圖)
執行時間 sumCol的執行時間是42.71% sumRow的執行時間是28.48% 雖然前述二者做的加法次數一 樣。由這裡可以看出row major 會比較快! 較快的原因可以用perf觀察 initTable花了29.66%的時間
呼叫圖 由左圖可以看到main分別呼叫 了四個函數。 也可以觀察到呼叫這四個函數 佔了總體多少時間
可是人家覺得不漂亮, 人家看不懂啦
valgrind shiwulo@vm:~/sp/ch02$ gcc -g table.c shiwulo@vm:~/sp/ch02$ valgrind --tool=callgrind ./a.out shiwulo@vm:~/sp/ch02$ kcachegrind callgrind.out.#####
產生的結果 (請注意每個function消耗的時間並不是很精準)
initTable居然佔了最多時間 (gprof的結果是sumCol)
小結 gprof的結果比較正確 使用valgrind可以獲得比較多的東西,但結果(特別是時間)是 不正確的 Valgrind是虛擬機器,類似java,產生一種容易profiling的中間碼,之後 再做統計 因此Valgrind並不是真的執行x86程式碼,時間上就不是那樣準確了
作業 使用Linux環境撰寫一個程式,這個程式必須算出「費伯納西數列」, 到第1,000,000個「費伯納西數」 費伯納西數列 繳交程式 所有.c、.h、makefile,執行檔名稱「fib」,助教執行make後必須產生fib 選出程式碼寫最好、註解最完整的三名同學,額外加5分,並公佈這三位同下 的程式碼