這原本是用Word寫的,可是後來我換了系統因此只能用markdown遷移而後寫了......
$\qquad$本文主要投食給那些在Windows下活了好久而後考試時發現須要用命令行來操做時困惑萬分以及以爲GDB很好吃的人
$\qquad$以及----linux
$\qquad$常常眼瞎看不見i++和j++的區別
$\qquad$常常訪問a[-1]然而使編譯器迫不得已(除非在使用O2的狀況下的明顯訪問越界)的人
...
$\qquad$正式地說,本文介紹GCC&&GDB命令在OI中的應用。c++
$\qquad$這須要提要嗎?本文就講GCC和GDB。
$\qquad$注意本文介紹的只是在OI中的應用,一些命令可能會有更高級的用法可是並不會介紹,有一些概念多是做者糊出來的能夠幫助你更好的理解。
$\qquad$本文做爲一個教程在語文語法方面不會很是嚴格,例如命令和指令常常混在一塊兒用(其實我也不知道有什麼區別),可是保證內容都是可用的。express
$\qquad$本文的有些部分操做在Windows x64下完成,有些在Linux x64下完成的。其中會對比賽的操做系統Noi Linux進行說明(其實大部分狀況下都是同樣的)。
$\qquad$Windows下的編譯器:gcc version 7.1.0 (i686-win32-dwarf-rev0, Built by MinGW-W64 project),7.1.0版本支持到c++17的標準。命令行用的是cmder(可有可無)。
$\qquad$Linux下用的編譯器:Thread model: posix
gcc version 6.4.0 20170724 (Debian 6.4.0-2),支持到c++17的標準
$\qquad$Noi Linux請參考NOI Linux的說明。
$\qquad$下面的指令說明部分${…}不是你要輸入的部分,它表明參數。指令後面的括號表示簡寫。
$\qquad$接下來的一份比較簡單的演示源文件,名爲XiaPi.cpp。數組
#include <cstdio> int main(){ int ans = 0; for(int i = 1; i <= 10; i++) ans ++; if(ans) std::puts("Hello World"); return 0; }
$\qquad$通常統稱GCC。然而通常編譯咱們用的GCC/G++編譯器,其實二者都能「編譯」C/C++源代碼,主要是由於編譯時二者會相似於「互相調用」。惟一不一樣的是GCC沒法連接C++的庫,因此gcc連接時須要加-lstdc++。反正G++是萬能的,那麼就用G++吧。
$\qquad$而後推薦一個好東西:makefile。由於筆者通常用的編譯命令比較長,例如:
g++ XiaPi.cpp -g -Wall -Wextra -std=c++17
$\qquad$因而你在源代碼目錄下建立makefile文件,而後裏面的語法參照:markdown
all:XiaPi.cpp g++ XiaPi.cpp -g -Wall -Wextra -std=c++17
$\qquad$也就是說XiaPi.cpp爲你的源文件名,後面是你的編譯命令。注意第二行的縮進是必須的。數據結構
$\qquad$G++在OI中經常使用命令解析:ide
g++ -v
$\qquad$就是把你的編譯器的信息打印出來。
$\qquad$不須要加文件名。
$\qquad$首先編譯文件的話要以g++ XiaPi.cpp開頭。
$\qquad$接下來是各類開關。全部開關均可以調換順序,可是g++後面必定接文件名(除了-v)。函數
-g2(-g)/-g3
$\qquad$打開調試開關,這樣這份代碼就能夠被調試。
$\qquad$-g3能夠調試宏定義的代碼工具
-O0/-O1/-O2/-Os/-O3
$\qquad$編譯器優化,通常本身不須要加,由於在O2及以上的等級都會致使不愉快的調試。
$\qquad$然而打開O2編譯等以上的優化能夠找出一些越界等問題。
$\qquad$Os爲O2.5優化,也就是打開O2但不增長代碼長度,有時候會有問題優化
-Wall -Wextra -Werror
$\qquad$三個命令都是指給代碼提供警告,其中:第一個爲基礎警告,第二個爲詳細的警告,第三個爲把全部警告視爲錯誤。
$\qquad$對於比較粗心的人有極佳的輔助效果。
-std=${standard(c++11 / c++14 / c++17)}
$\qquad$指定C++標準。然而通常來講考試都不會加這個選項,因此通常平時加加就差很少了
$\qquad$主要是鍛鍊一些裝逼寫高級代碼的能力。
$\qquad$注意不是全部的編譯器版本都支持各類標準,目前經常使用標準有C++11,C++14,C++17。
$\qquad$這些標準的區別在於語法不一樣,例如:
$\qquad$register標識在c++17下會被提示禁用(其實很多標識都會被忽略)
for(auto it : vector){ ... }
$\qquad$在C++11及以上可使用
-o ${filename}
$\qquad$後面可接文件名,表示輸出的可執行文件名。可是千萬別寫相似於-o XiaPi.cpp的東西......
-E
$\qquad$輸出預處理器處理過的東西,通常是須要在指令後面加上 > XiaPi.e纔會輸出到文件。通常沒什麼用,可是你能夠觀察到前人對你的代碼作出了多大的努力。
$\qquad$例以下面一張圖是-E處理後的XiaPi.cpp。
[預處理][預處理]
$\qquad$這個有幾千行,然而源代碼只有幾行。
-S
$\qquad$出編譯後的彙編代碼,有時候能夠做爲分析優化的工具。會輸出一個.s的文件,像這樣:
.file "XiaPi.cpp" .def ___main; .scl 2; .type 32; .endef .section .rdata,"dr" LC0: .ascii "Hello World\0" .text .globl _main .def _main; .scl 2; .type 32; .endef _main: LFB44: .cfi_startproc pushl %ebp .cfi_def_cfa_offset 8 .cfi_offset 5, -8 movl %esp, %ebp .cfi_def_cfa_register 5 andl $-16, %esp subl $32, %esp call ___main movl $0, 28(%esp) movl $1, 24(%esp) L3: cmpl $10, 24(%esp) jg L2 addl $1, 28(%esp) addl $1, 24(%esp) jmp L3 L2: cmpl $0, 28(%esp) je L4 movl $LC0, (%esp) call _puts L4: movl $0, %eax leave .cfi_restore 5 .cfi_def_cfa 4, 4 ret .cfi_endproc LFE44: .ident "GCC: (i686-win32-dwarf-rev0, Built by MinGW-W64 project) 7.1.0" .def _puts; .scl 2; .type 32; .endef
$\qquad$而後就是GDB部分。GDB的安裝(若是和編譯器分開了)應和編譯器配套,能夠經過gdb -v查看你的版本信息。同時在Windows下彷佛沒有Linux下那麼流暢。有時候莫名崩潰,例如在GDB內運行程序以後應該等待(至少個人兩臺Windows都會這樣)線程都開出來了才行,即顯示了相似於
[New Thread 11064.0x3d2c] [New Thread 11064.0x3dbc] [New Thread 11064.0x1f00] [New Thread 11064.0x3dc4]
的東西再輸入數據,不然可能崩潰。
$\qquad$首先咱們在命令行內使用 gdb ${可執行文件名}
進入GDB,例如不加-o的狀況下,Windows可以使用gdb a(可省略後綴名),Linux下可以使用gdb a.out進入。若是使用了-o,則後面接你的可執行文件名。
$\qquad$gdb每次接受一條命令,回車則爲重複執行上一條命令。
$\qquad$通常若是當前行前面有(gdb)
那麼就是在等待用戶命令,不然多是等待輸入或者等待程序運行。
$\qquad$(擴展命令)若是不在gdb內的程序進入了一個死循環什麼的,而後你想在當前狀態迅速調試它,那麼能夠再開一個終端,而後輸入gdb ${可執行文件名} ${進程pid}
去控制它
中止點
$\qquad$可使正在運行的程序中斷在某個點上。有breakpoint(對應操做break)和watchpoint(對應操做watch)之分(還有一個catchpoint,可是OI中不用)。breakpoint接受一個位置,當到達這個位置的時候停下。Watchpoint接受一個表達式,當表達式的值有變化的時候暫停程序。
查看
$\qquad$一直監視一個變量,對應操做display。
$\qquad$有時候gdb接受一個編號,有時候接受一個編號或範圍,編號就是一個數字,範圍寫做i-j
$\qquad$接下來的操做都是在gdb內執行的。
help
help help ${function_name}
最重要的命令,獲取即時幫助
file
file ${command_name}
$\qquad$這個命令是當你gdb後面沒有輸入文件名或者輸入錯誤的時候能夠載入文件,當gdb中文件有變化(不要太大的變化,可執行文件不能變)的時候能夠重載文件。
list
list(l) list(l) ${function_name} list(l) ${line_number}
$\qquad$打印代碼。第一個是自動連續顯示,第二個從某行開始打印。list會有個相似於計數器的東西,每次打印一片,Ctrl+C退出打印,回車繼續打印。例如:
#include <cstdio> int main(){ int ans = 0; for(int i = 1; i <= 10; i++) ans ++; if(ans) std::puts("Hello World"); return 0; }(gdb)
$\qquad$若是打印到了文件尾部,則會有:
Line number 9 out of range; XiaPi.cpp has 8 lines.
$\qquad$此時若是繼續list會一直提示。因此咱們要list 1回到文件首部。
run(r)
run(r)
$\qquad$這個命令會運行程序,直到遇到錯誤或遇到斷點。運行此命令前請保證已經設置了斷點,除非你利用這個檢查你程序的錯誤。
$\qquad$注意到咱們斷點設置在幾行(例如第五行),程序會在第五行暫停,但注意程序此時並無運行第五行,例如:
Thread 1 hit Breakpoint 1, main () at XiaPi.cpp:5 5 for(int i = 1; i <= 10; i++) ans ++;
$\qquad$表示程序並無運行到第五行。即在之後看到行號n的時候程序尚未運行第n行的。
next(n)
next(n) nexti(ni)
$\qquad$向下走一步(一行),但不進入函數
$\qquad$後面那個是在彙編中下一行
step(s)
step(s) stepi(si)
$\qquad$向下走一步(一行),且若是有函數就進去,內聯或宏定義不會進去
$\qquad$後面那個是在彙編中下一行,而且進入call
skip
skip ${function_name} skip ${file_name} skip delete\disable\enable
$\qquad$在step時跳過這個函數或文件
$\qquad$delete\disable\enable能夠參考其餘命令的意思
finish
finish
$\qquad$運行直到退出這個函數
until
until
$\qquad$運行直到當前循環結束
continue(c)
continue(c)
$\qquad$繼續運行直到下一個斷點
jump
jump ${line_number}
$\qquad$直接跳到某行,若是發生了函數棧的邏輯錯誤,例如這個函數沒有執行完而後直接調到另外一個函數,可是命令不會改變棧的內容,因此會有一些不可預料的錯誤
break(b)
break(b) break(b) ${function_name} break(b) (${file_name}::) ${line_number} break(b) ${line_number} ${expression}
$\qquad$斷點操做,第一個是查看全部斷點,注意斷點都有編號。
$\qquad$第二個是在某個函數開頭設置斷點。
$\qquad$第三個使在某行設置斷點,但程序並不會運行該行。
$\qquad$例如
Breakpoint 1 at 0x401611: file XiaPi.cpp, line 6.
表示在XiaPi.cpp中運行到了第6行,但尚未執行第六行的代碼
$\qquad$若是趕上多文件(例如交互題)怎麼辦?你能夠用break ${file_name}::${line_number}
來設置指定文件的斷點
$\qquad$(擴展用法)最後一個是一個高級用法:當expression知足的時候斷點生效,$數據結構等比較麻煩的東西特效$
save && source
save breakpoints ${file_name} source ${file_name}
$\qquad$從某DL那聽來的,好像也比較實用,就是在每次調試完以後但願下一次調試使用一樣的斷點
$\qquad$就能夠首先保存到文件,而後下一次打開的時候source一下
condition
condition ${break_number} ${expression}
$\qquad$上面命令的一個附屬命令,修改某個斷點上的判斷表達式
commands
commands ${break_number} ${commands} end
$\qquad$在遇到某個斷點而且被停住的時候執行命令(GDB內的命令,例如打印某個變量)
$\qquad$配合if能夠很好地用在一個複雜的程序提取關鍵信息
$\qquad$例如一個使用狀況:
(gdb) commands 1 Type commands for breakpoint(s) 1, one per line. End with a line saying just "end". >print ans >end
ignore
ignore ${break_number} ${times}
$\qquad$忽略某個斷點的條件若干次
watch
watch ${expression} rwatch ${expression} awatch ${expression}
$\qquad$設置觀察點,當一個表達式(變量)發生一下時間的事情時中斷程序
$\qquad$watch:表達式值有變化,rwatch:表達式被讀,awatch:b表達式被讀或被寫(後面兩個通常都對於表達式),其中awatch比較適用於一些你認爲一個變量被玄學地改動的時候能夠用的
clear
clear clear ${line_number} clear ${function_name}
$\qquad$刪除中止點(不僅有斷點)
$\qquad$後面就是指定了行數或函數名
delete(d)
delete(d) delete(d) ${break_number}
$\qquad$刪除斷點,支持範圍(這個意思是這個命令能夠把編號替換成爲範圍)
disable\enable
disable ${break_number} enable ${break_number} enable ${break_number} once enable ${break_number} delete
$\qquad$暫時禁止某個斷點,啓用某個斷點,once表示用一次以後disable,delete表示用一次以後delete
$\qquad$支持範圍
checkpoint
checkpoint
$\qquad$在當前位置設置一個檢查點,以便於從新調試程序的時候快速恢復狀態,注意,它的簡單原理就是拷貝進程,因此當前checkpoint進程跑完以後就沒有checkpoint,若是須要重複使用就在restart以後立刻checkpoint
restart
restart ${check_number}
$\qquad$在第幾個checkpoint重啓程序
print
print print (${file_name}/${function_name}::)${expression} print {array_pointer}@${length} printf ${format},${expression}...
$\qquad$第一個是輸出歷史查看過的變量
$\qquad$而後是打印表達式的值(開了-g3可使用部分宏定義),同時能夠指定文件或函數(能夠跨函數),表達式內容許使用函數,而且可使用相似於a=1
的表達式來賦值等等
$\qquad$而後是查看某個數組指針開始的多少個值,若是是二維數組,能夠用print *a[10]@100
查看
$\qquad$後面那個是按照printf的格式打印變量
$\qquad$注意有些STL函數或結構體函數不能被查看,這個也沒有辦法總結,本身按照經驗吧
display(disp)
display(disp) ${expression} undisplay(undisp) ${display_number} delete display ${display_number} disable ${display_number} enable ${display_number}
$\qquad$創建查看,一直打印變量,只要有程序停下或者輸入命令都會顯示,注意不要弄得太多太複雜了,不然會很卡
$\qquad$後面兩個都是刪除查看
$\qquad$後面兩個和前面的同樣
call
call ${expression}
$\qquad$執行某個表達式(函數),而且打印返回值,和print不一樣的是當返回值爲void的時候call不會打印值
set
set ${var_name}=${number} set var ${var_name}=${number} set args ${args}...
$\qquad$賦值,若是賦值對象是gdb內的關鍵字的名詞,那麼須要用var聲明
$\qquad$最後一條能夠設置程序的參數
ptype
ptype ${struct/union/class/expression}
$\qquad$能夠查看某結構體的具體類型(具體到union/int[2]),能夠檢查一些隱式轉換以防止被卡浮點什麼的
whatis
whatis ${struct/union/class/expression}
$\qquad$能夠查看某結構體的具體類型(具體到const number)
$\qquad$和上一命令的區別舉例:
(gdb) ptype d type = const union { int i[2]; double d; } (gdb) whatis d type = const number
d是某個庫內定義的東西
info
info 查看全部info可用參數 info address ${var_name} -- 打印指定變量在內存中的位置 info all-register -- 打印全部寄存器狀態 info args -- 打印當前函數棧的參數 info breakpoints (${break_number}_ -- 答應斷點信息 info checkpoints (${check_number}) -- 打印checkpoint信息 info display (${display_number}) -- 打印display的信息 info functions -- 打印函數信息 info handle -- 打印處理signal的信息 info line (${line_number}) -- 打印行信息,默認當前行 info program -- 打印程序狀態 info registers (${register_name}) -- 打印寄存器狀態,不要"$",例如`info register eax` info signals -- 打印信號信息 info skip -- 查看skip info source -- 查看源碼信息 info sources -- 查看源碼以及依賴庫信息 info stack -- 查看棧信息 info symbol -- 查看某個地址上的變量 info types -- 打印全部定義過的類型 info variables -- 打印全部變量 info watchpoints -- 打印觀察點信息
就在上面
breacktrace(bt)
breacktrace(bt) breacktrace(bt) ${number}
$\qquad$查看棧信息,後面的數字表示查看幾層,正數從棧頂開始,不然從棧底開始(一次從棧頂/棧底開始)
frame
frame ${stack_numbwe} down/up ${number}
$\qquad$跳到第幾層棧(不會對棧形成影響)
$\qquad$向上/下移動當前棧
handle
handle ${signal_name} ${keyword}
$\qquad$對某個信號進行處理
$\qquad$signal_name有
SIGABRT -- 進程中止運行 SIGALRM -- 警告鍾 SIGFPE -- 算述運算例外 SIGHUP -- 系統掛斷 SIGILL -- 非法指令 SIGINT -- 終端中斷 SIGKILL -- 中止進程(此信號不能被忽略或捕獲) SIGPIPE -- 向沒有讀的管道寫入數據 SIGSEGV -- 無效內存段訪問 SIGQOUT -- 終端退出 SIGTERM -- 終止 SIGUSR1 -- 用戶定義信號 SIGUSR2 -- 用戶定義信號 SIGCHLD -- 子進程已經中止或退出 SIGCONT -- 若是被中止則繼續執行 SIGSTOP -- 中止執行 SIGTSTP -- 終端中止信號 SIGTOUT -- 後臺進程請求進行寫操做 SIGTTIN -- 後臺進程請求進行讀操做
$\qquad$keyword有
nostop 發出信息,不中止程序 stop 發出信息病中止程序 print 僅發出信息 noprint 不發出信息 pass/noignore 程序處理信號 nopass/ignore 不讓程序處理信號
$\qquad$有時候但願能夠暫時忽略某個信號而繼續程序可使用,但不保證每次均可以
print ${var_name}@${length}
$\qquad$查看內存中某個變量後面的值。用這個能夠檢測你的錯誤是否和下標越界有關
search
$\qquad$只是查找源文件的文本而已
max-value-size
$\qquad$這個是限制gdb一次可以查看的數組的大小的限制
$\qquad$通常其實夠用了,可是若是你的數組內重複數據比較多的話那麼就能夠把這個開到儘量大
set max-value-size ${size} set max-value-size unlimited
$\qquad$第一個能夠設定限制大小
$\qquad$能夠取消限制
也許是完結了吧