<style> .reveal .slides { text-align: left; font-size:28px; } </style> # debug 技巧 ---- - linux 指令 - 對拍 - Debugging template --- ## 為什麼要學 linux 指令 ? ---- ## ICPC 國際大學生程式設計競賽 https://icpc2022.ntub.edu.tw/onsite/contest-environment/ ![](https://i.imgur.com/nbiaerF.png) ---- ## NCPC 全國大專電腦軟體設計競賽 https://ncpc.ntnu.edu.tw/environment/final.html ![](https://i.imgur.com/x6s44lJ.png) ---- - 對開發者較友善 - 更多好用的東西 - 等等對拍也會用到 ---- ## 如果我的電腦是 Windows ? - 雙系統 - Windows Subsystem of Linux (WSL) ---- ## Windows Subsystem of Linux (WSL) https://zhuanlan.zhihu.com/p/47541491 https://hackmd.io/@billsun/BJByCIUHf?type=view <div style="position: absolute; right: 90%;"> ![](https://i.imgur.com/BdlVmp2.png) </div> <div style="position: absolute; right: 50%;"> ![](https://i.imgur.com/TL289F9.png =x300) </div> <div style="position: absolute; right: 10%;"> ![](https://i.imgur.com/Q8xnHrw.png =x300) </div> ---- ## linux 基本指令 ```shell= cd .. 回到上一層資料夾 cd ../.. 回到上上層資料夾 cd test 到當前目錄的test資料夾 ls 顯示當前資料夾的檔案 cat a.cpp 印出當前檔案的內容 mkdir test 在當前目錄建立 test 的資料夾 rm a.cpp 刪除 a.cpp 的檔案 ``` ---- ## 編譯檔案 g++ solve.cpp -o main -std=c++14 -fsanitize=undefined -Wall -Wextra -Wshadow ```shell= g++ solve.cpp 編譯solve.cpp的檔案成 a.out 檔 g++ solve.cpp -o ac.out 編譯solve.cpp的檔案成 ac.out 檔 g++ solve.cpp -std=c++14 編譯solve.cpp的檔案成 a.out 檔 並且編譯版本為 c++14 ./a.out 執行 a.out 檔 ``` ## 其他編譯參數 ```shell= -fsanitize=undefined 插入各種undefined behavior檢查,會在執行期輸出錯誤訊息 -Wall -Wextra 把warning都開起來,常能預防bug發生 -Wshadow 當有宣告了相同變數名稱的情形發生時予以警告 ``` ---- g++ solve.cpp -o main -std=c++14 -fsanitize=undefined -Wall -Wextra -Wshadow ---- 難道每次都要打那麼長嗎 ? ---- ## Alias 可以使用 alias 把原本要打的那串 define 掉 ```bash alias [name]='[value]' ``` 等同於 c++ 內的 #define #define ll long long ```bash alias g++ = `g++ -std=c++14 -fsanitize=undefined -Wall -Wextra -Wshadow` ``` ---- 使用 Alias 之後 ```bash g++ solve.cpp ``` 等價於 ```bash g++ -std=c++14 -fsanitize=undefined -Wall -Wextra -Wshadow solve.cpp ``` ---- ## 產生質因數 ```bash= factor 100 ``` --- ## debug 競賽中或者平常寫題目時,如果遇到 bug 不能及時找到 有以下方法 1. 團體賽中,可以把 code 印出來自己到旁邊 debug,給其他隊友上機寫其他題目 (前提為有其他題目可以開) 2. 團體賽中,可以把 code 印出來給隊友 debug, (前提為能有效跟隊友溝通並且看得懂你的code) 3. 好好寫對拍 ---- ## 對拍 ![](https://i.imgur.com/EVCPEya.png) ---- ## 步驟 1. **寫對拍的 script** 2. 寫一份暴力程式碼 ac.cpp 3. 寫一份產生隨機測資的程式碼 ```gen.py/gen.cpp``` ---- ## 對拍的 script 先將兩份程式碼分別編譯成 ac, wa 不斷用 ``gen.py`` 產生測資放到 input 檔案裡面 再把 input 檔案分別為給 ac, wa 兩份執行檔 用 for 迴圈一直跑到 output 內容不同為止 set -e 可以偵測程式 RE 時會停下來 ```shell= set -e g++ ac.cpp -o ac g++ wa.cpp -o wa for ((i=0;;i++)) do echo "$i" python3 gen.py > input ./ac < input > ac.out ./wa < input > wa.out diff ac.out wa.out || break done ``` ---- 跑到停下來後有兩種可能 1. RUNTIME ERROR ![](https://i.imgur.com/Ylikjj1.png) 2. WRONG ANSWER ![](https://i.imgur.com/yI10kke.png) ---- ## 為甚麼要用對拍 - 找不到好的 case 能讓他自己跑 先寫其他題目 - 寫暴力程式碼的時間遠比多吃一次 20 penalty 還要來的划算 - 對拍可以重複驗證程式正確性,de 完原本的 bug 可以再跑一次對拍 因此當比賽時,如果對於錯誤毫無頭緒, 不應該只是盯著程式碼看,而是開始寫對拍才是最有效率的做法 ---- 而平時練習、作業找不到bug的情況也應該要先對拍 真的找不到問題 對拍也對不到 再去問其他電神錯在哪 ---- ## 步驟 1. 寫對拍的 script 2. **寫一份暴力程式碼 ac.cpp** 3. 寫一份產生隨機測資的程式碼 ```gen.py/gen.cpp``` ---- ## 寫一份暴力程式碼 ac.cpp 聽起來好麻煩,為了 debug 還要多寫一份 code 而且不一定寫得出來還有可能錯,怎麼辦? ---- 如果連暴力的程式碼都寫不出來 那你應該多練練你的實作能力... 如果不知道怎麼寫請參考早上 [brute force](https://hackmd.io/@LeeShoWhaodian/bruteforce) 講義 善用 next_permutation 以及好好熟悉遞迴 沒有暴力寫不了的程式碼 只有你不練不夠實作能力 ---- ## 步驟 1. 寫對拍的 script 2. 寫一份暴力程式碼 ac.cpp 3. **寫一份產生隨機測資的程式碼 ```gen.py/gen.cpp```** ---- ## 產生隨機測資 (python) ```python= from random import * n = randint(1, 100) # 隨機產生 1~100的整數 ch = chr(ord('a') + randint(0, 25)) # 隨機產生 'a'~'z' 其中一個字元 choiceSet = sample(s, 4) # 從集合 s 選出 4 個不同的元素 choiceSet = sample(range(1, n+1), 4) # 從整數 1~n 選出 4 個不同的元素 shuffle(arr) # 把序列 arr 順序打亂 ``` ---- ## 隨機產生一組 1~n 的 permutation ```python= from random import * n = randint(3, 6) arr = [i+1 for i in range(n)] shuffle(arr) print(n) print(*arr) #輸出 arr 的內容並且用空格隔開 ``` ---- ## 隨機產生一個長度為 n 的小寫字母字串 ```python= s = "" n = randint(1, 10) for i in range(n): s += chr(ord('a') + randint(0, 25)) print(s) ``` ---- ## 隨機產生一棵樹 每次從比自己小的節點選一個當連接自己的邊 ```python= from random import * n = randint(3, 6) print(n) for i in range(2, n+1): print(randint(1, i-1), i) ``` ---- ## 隨機產生無自環無重邊的一張連通圖 無自環 可以判斷兩個點不相同 無重邊 可以用 dict/map 紀錄哪些邊用過了 連通圖 = 樹 + 一些額外的邊 ---- ## 隨機產生無自環無重邊的一張連通圖 ```python= from random import * n = randint(5, 10) m = randint(n-1, n+3) print(n, m) edge = list() #construct tree for i in range(2, n+1): x = randint(1, i-1) y = i edge.append([min(x, y), max(x, y)]) print(x, y) #add extra edge for i in range(m-(n-1)): x = randint(1, n) y = randint(1, n) while x == y or [min(x, y), max(x, y)] in edge: x = randint(1, n) y = randint(1, n) print(x, y) edge.append([min(x, y), max(x, y)]) ``` ---- ## 產生隨機測資 (c++) [Don't use rand(): a guide to random number generators in C++](https://codeforces.com/blog/entry/61587) mt19937 是 c++ 的一種隨機產生器 ```cpp= mt19937 gen(chrono::steady_clock::now().time_since_epoch().count()); int randint(int lb, int ub) { return uniform_int_distribution<int>(lb, ub)(gen); } ``` ---- ## sample code(c++) ```cpp= mt19937 gen(chrono::steady_clock::now().time_since_epoch().count()); int randint(int lb, int ub) { return uniform_int_distribution<int>(lb, ub)(gen); } int main(){ int n = randint(1, 10); //產生 0~10 之間的任意整數 char ch = 'a'+randint(0, 25); //產生 a-z 之間的任意字母 vector<int> ord(n); iota(ord.begin(), ord.end(), 1); shuffle(ord.begin(), ord.end(), gen); cout << n << endl; for(int i : ord) cout << i << " "; cout << endl; } ``` ---- ## 練習產測資 隨便拿一場比賽練習每一題如何產測資 [2023 I2CP Pretraining Final Contest](https://codeforces.com/group/dnlUA4rsoS/contest/419656/problems) ---- ```shell= set -e g++ ac.cpp -o ac g++ wa.cpp -o wa for ((i=0;;i++)) do echo "$i" python3 gen.py > input ./ac < input > ac.out ./wa < input > wa.out diff ac.out wa.out || break done ``` --- ## How to Debug ? ---- ## Debug --- Print 大法 可能有問題的那段程式碼 把變數 print 出來 ```cpp= cout << "Before" << endl; for(int i = 0; i < n; i++){ cout << arr[i] << ' '; } cout << endl; . . . cout << "After" << endl; for(int i = 0; i < n; i++){ cout << arr[i] << ' '; } cout << endl; ``` ---- ## 這樣會有什麼問題 ? <span>1. debug 完 bug 忘記註解/刪掉<!-- .element: class="fragment" data-fragment-index="1" --><div style="position: absolute; left: 5%;top: 120%"> ![](https://i.imgur.com/xAoxWx6.png) </div><!-- .element: class="fragment" data-fragment-index="1" --></span> <span> 2. debug 完把 printer 註解,但沒有註解乾淨<!-- .element: class="fragment" data-fragment-index="2" --><div style="position: absolute; left: 5%;top: 120%"> ![](https://i.imgur.com/4nJz0dZ.png) </div><!-- .element: class="fragment" data-fragment-index="2" --></span> <span> 3. 好麻煩喔 先把所有 cout 都註解la 再把要輸出的反註解<!-- .element: class="fragment" data-fragment-index="3" --> <div style="position: absolute; left: 5%;top: 120%"> ![](https://i.imgur.com/KR5A7tQ.png) </div><!-- .element: class="fragment" data-fragment-index="3" --></span> <span> 4. 喔 好像多註解到要輸出的答案了...<!-- .element: class="fragment" data-fragment-index="4" --><div style="position: absolute; left: 5%;top: 120%"> ![](https://i.imgur.com/bzAzgms.png) </div><!-- .element: class="fragment" data-fragment-index="4" --></span> ---- ## 這樣會有什麼問題 ? penalty += 80(20 * 4) ![](https://i.imgur.com/2BYxsdb.png) ---- ## cerr debug 時,很常會用cout輸出每個變數的值 在這邊建議輸出debug的值時使用cerr 他輸出的東西只會出現在 Standard error stream 不會輸出在 Standard output stream 答案比對是比對 Standard output stream 的內容 因此就算忘記刪也不會造成影響 ```cpp= // __LINE__ 會輸出呼叫的位置在程式碼第幾行 cerr << "This is debug message on " << __LINE__ << " line : " << x << endl; ``` ---- ## cerr 問題 如果沒有刪掉可能造成 TLE ```cpp for(int i = 0; i < n; i++){ x *= 2; arr[x] += v; for(int j = 0; j < n; j++){ cerr << arr[j] << " "; } cerr << endl; } ``` 原本可能 $O(N)$ 的程式碼變成 $O(N^2)$ 或者常數太大 (輸出數量變成原本的兩倍) ---- ## Debugging template 讓你更好 debug 的工具 ---- ```cpp= [|1-6|7-12|] #ifdef LOCAL // =========== Local =========== void dbg() { cerr << '\n'; } template<class T, class ...U> void dbg(T a, U ...b) { cerr << a << ' ', dbg(b...); } template<class T> void org(T l, T r) { while (l != r) cerr << *l++ << ' '; cerr << '\n'; } #define debug(args...) (dbg("#> (" + string(#args) + ") = (", args, ")")) #define orange(args...) (cerr << "#> [" + string(#args) + ") = ", org(args)) #else // ======== OnlineJudge ======== #pragma GCC optimize("O3,unroll-loops") #pragma GCC target("avx2,bmi,bmi2,lzcnt,popcnt") #define debug(...) ((void)0) #define orange(...) ((void)0) #endif ``` ---- ## #ifdef LOCAL 如果有 define LOCAL 那就會... ```cpp= #ifdef LOCAL // =========== Local =========== void dbg() { cerr << '\n'; } template<class T, class ...U> void dbg(T a, U ...b) { cerr << a << ' ', dbg(b...); } template<class T> void org(T l, T r) { while (l != r) cerr << *l++ << ' '; cerr << '\n'; } #define debug(args...) (dbg("#> (" + string(#args) + ") = (", args, ")")) #define orange(args...) (cerr << "#> [" + string(#args) + ") = ", org(args)) ``` 否則就會... ```cpp= #else // ======== OnlineJudge ======== #pragma GCC optimize("O3,unroll-loops") #pragma GCC target("avx2,bmi,bmi2,lzcnt,popcnt") #define debug(...) ((void)0) #define orange(...) ((void)0) #endif ``` ---- 在本機 define LOCAL ```cpp! g++ solve.cpp -D LOCAL ``` 則編譯時就會 define LOCAL 但每次都要打 -D LOCAL 很麻煩... ---- 還記得 alias ```bash alias g++ = `g++ -std=c++14 -fsanitize=undefined -Wall -Wextra -Wshadow` ``` 改成 ```bash alias g++ = `g++ -std=c++14 -fsanitize=undefined -Wall -Wextra -Wshadow -D LOCAL` ``` :happymention: ---- ## debug 變數 debug(x) ```cpp! string x = "abc"; debug(x); int y = 123; double z = 1.23; debug(y, z); ``` ---- ## debug 範圍 orange(begin, end) ```cpp= int arr[5]={2,1,4,3,5}; orange(arr, arr+5); vector<string> strVec = {"Alice", "Bob"}; orange(strVec.begin(), strVec.end()); ``` ---- ```cpp= ```cpp= #ifdef LOCAL // =========== Local =========== void dbg() { cerr << '\n'; } template<class T, class ...U> void dbg(T a, U ...b) { cerr << a << ' ', dbg(b...); } template<class T> void org(T l, T r) { while (l != r) cerr << *l++ << ' '; cerr << '\n'; } #define debug(args...) (dbg("#> (" + string(#args) + ") = (", args, ")")) #define orange(args...) (cerr << "#> [" + string(#args) + ") = ", org(args)) #else // ======== OnlineJudge ======== #pragma GCC optimize("O3,unroll-loops") #pragma GCC target("avx2,bmi,bmi2,lzcnt,popcnt") #define debug(...) ((void)0) #define orange(...) ((void)0) #endif ``` ---- ## assert 他用來判斷一個條件是否成立, 如果條件成立則不會發生任何事 如果條件不成立,則會造成程式RE(Runtime Error) 通常用於 debug 不確定會不會錯 或者想 submit 時 不確定有沒有問題用的 ```cpp= assert(x <= 5); ``` ---- ![](https://i.imgur.com/1t3GKrZ.png =800x) ```cpp= [|10-11|1-7,12|13|] long long fpow(int x, int y){ long long ret = 1; for(;y;y>>=1, x*=x){ if(y&1) ret = ret * x; } return ret; } int main(){ cin >> n; auto [x, y] = solve(n); assert(fpow(x, y) * y + fpow(y, x) * x == n); cout << x << " " << y << endl; } ``` ---- 或者想猜測資,判斷是否大於某個值,就可以assert 再加上 TLE 一起用 ~~每次可以把測資分三半去猜測資 複雜度 $O(log_3N)$~~ ```cpp= cin >> n; if(n >= 10){ // RE assert(0); } else if(n > 5){ // TLE while(1){} } else{ // WA return 0; } ``` ---- [Benq's submission](https://codeforces.com/submissions/Benq) [Who is Benq?](https://www.youtube.com/watch?v=xiDEsX0_MGE) ![](https://i.imgur.com/DaEuKsI.png) - 溢位 - 陣列超出範圍 - 邊界測資 n=1 n=2 n=bound --- ![](https://xqimg.imedao.com/159682b5dc02a1f3fdfcf1c1.jpeg!800.jpg =500x)
{"metaMigratedAt":"2023-06-17T19:57:05.006Z","metaMigratedFrom":"YAML","title":"debug 技巧","breaks":true,"slideOptions":"{\"transition\":\"fade;\"}","contributors":"[{\"id\":\"19f09ccf-6b99-452f-971f-955cfc1657f3\",\"add\":13678,\"del\":1767}]","description":"linux 指令"}
    1815 views
   Owned this note