您現在的位置是:首頁 > 網路遊戲首頁網路遊戲
程式設計競賽時程式碼出現漏洞應該怎麼排查?
- 2022-06-30
輸出超限怎麼解決
原文地址:
https://www。itzhiyin。cn/309。html
前言
可能很多人都遇到過一個問題,就是在寫出程式碼之後發現報出了各種各樣的錯誤,但是卻找不到根源。本篇織音將簡要講述如何快速的排查大部分錯誤。
執行錯誤
顧名思義,就是程式碼執行時出現了錯誤,錯誤種類有很多,我們舉出比較常見的一部分來說。
除零錯誤
除零錯誤必然發生在進行除法運算的地方,這時候我們需要考慮以下幾點:
是否是程式編寫時失誤導致除數運算錯誤而出現了0
是否沒有考慮某些特殊情況導致除數出現了0
越界訪問
越界訪問也有兩種,即陣列越界訪問和野指標。
陣列越界訪問
這個很好理解,就是訪問陣列時下標超出了陣列的範圍。(注意:並非所有越界訪問都會導致異常。)出現數組越界的大多數情況是遍歷陣列是迴圈變數的更新或範圍有錯誤,或者是呼叫一些系統函式(比如memset)時傳入的引數有誤。
野指標
野指標就是指標指向的位置是不可知的
這裡我們舉出一個例子:
int* createPoint() {
int k = 0;
return &k;
}
int main() {
int* point = createPoint();
int* array = malloc(3 * sizeof(int));
free(array);
//do something。。。
return 0;
}
這裡列出了兩種比較常見的造成野指標出現的情況,第一種就是函式返回(或透過其它方式傳出)了一個指向區域性變數的指標,當代碼執行到區域性變數的作用域之外時變數就會被銷燬(記憶體有可能不會立即銷燬,但是該區域記憶體隨時有可能被重新使用),此時這個指標便指向了一個無效的區域。第二種情況就是使用malloc分配了記憶體,後面又使用了free銷燬記憶體,但是注意,free只會銷燬記憶體而不會銷燬指標,也就是說此時指標依舊指向被銷燬的記憶體。
當使用野指標時,能不能計算出正確的結果以及能否不崩潰都是一個完全看臉的問題。
呼叫棧溢位
遞迴深度過大導致呼叫棧溢位
非零返回
按照標準規定,當程式正常結束時main函式應當返回 0,否則返回非0值。如果你的程式碼種主函式返回了非0值,不論運算結果是否正確,測試平臺都會當作你的程式碼沒有正確結束。出現這種情況時一部分平臺會直接報“執行錯誤”,也有一部分平臺會單獨報出“非零返回”。
浮點錯誤
浮點錯誤大多也是浮點運算過程中出現了除零操作
記憶體超限
這種錯誤出現的比較少,因為競賽題給的記憶體大多比較大,除非用了體積比較龐大的資料結構,不然很難出現這種錯誤。
時間超限
時間超限也有多種情況,我們列出一部分。
死迴圈
迴圈中忘記更新迴圈變數或更新了錯誤的值,導致迴圈永遠不會結束,我們給出一個典型的“範例”:
int main() {
for (int i = 0; i != 10; -i) {
//do something。。。
}
printf(“不可達的程式碼!”);
return 0;
}
這段程式碼除非迴圈體中使用了break、goto一類的控制流語句,否則不可能跳出迴圈。
等待輸入
使用輸入流函式時程式碼中需要輸入的資料比實際應該輸入的資料多,導致程式卡在輸入流函式中一直等待外部繼續輸入資料,實際上已經沒有任何資料會輸入了。常見的情況是題目要求以EOF結束輸入但是程式碼卻忘了判斷。
int main() {
int n;
while (true) {
scanf(“%d”, &n);
//do something。。。
}
return 0;
}
計算時間過長
出現這種問題表明程式碼的演算法不合理或不適用當前情況,執行時間超出題目要求時長。解決方法有這幾種:
最佳化演算法(適用於演算法大體方向正確但有最佳化餘地)
使用更優的演算法
減少資料複製(複製資料不僅消耗記憶體空間也會佔用時間)
用高效的函式替代迴圈(常見的是memset、memmove、memcpy等)
輸出超限
程式執行結果過長,出現這種情況答案肯定是錯了,仔細讀一遍題,看看是不是什麼資料不需要輸出或者理解錯了題意,沒有的話就是程式碼寫錯了。
答案錯誤
答案錯誤是最常見也是最令人頭疼的錯誤,因為大多數情況下,競賽中提交的程式碼出現答案錯誤時,平臺不會給出相關資料,只會報出“答案錯誤”四 個 大 字。
首先我們清除一個點,如果程式碼的整體思路是對的,除非是手誤,基本上只會在特殊資料上出問題,就是某些特殊輸入沒有考慮到,自己手算以下結果然後和程式碼執行結果比較一下,檢查是否正確是一種比較高效的方法。
如果完全沒有頭緒,甚至連程式碼有什麼特殊情況會出問題都找不出來,可以採用控制流排除法。例如下面的程式碼:
int main() {
int n;
scanf(“%d”, &n);
//code1
if (。。。) {
//code2
} else if (。。。) {
if (。。。) {
//code3
} else {
//code4
}
} else {
//code5
}
return 0;
}
排查錯誤時我們可以從控制流語句出發,採用從內向外排查的方法。我們先找出深度最深的控制流語句,比如這裡的code3和code4,我們算出會讓程式碼進入到這兩個語句塊的輸入,然後輸入到程式中檢查是否正確。如果發現答案錯誤,那麼說明是到達這段程式碼的過程中或者是這段程式碼的計算有問題。如果沒有錯誤就到其它的語句中檢查,比如code2和code5,這樣就縮小了範圍。
在儘可能的縮小範圍之後,如果還是看不出錯誤,可以使用printf一類的輸出流函式輸出程式的過程量,監視程式碼運算的中間值是否正確,如果出現錯誤,只要找到第一個開始錯誤的點就能更容易的找到問題的根源。
這篇部落格提到的方法都是一些輔助排查的方法,使用上述方法也不一定能夠找到錯誤點。想要真正快速地找到錯誤,首先是必須理解自己寫了什麼,如果連自己寫的是什麼都不知道想要排查錯誤簡直是痴人說夢。
如果你還有什麼好辦法、想知道的或者想補充的,歡迎在評論區中寫下你的想法。