2010-11-03 48 views
0

我的代碼在unix機器上當天早些時候工作,但是當在Windows下編譯時,它給了我完全奇怪和不正確的輸出。C奇怪的bug ...把​​我的頭髮拉出來

由於我們的代碼將基於unix上的編譯進行標記,所以我認爲這很好。但是現在我剛剛完成了對代碼的重構(基本上只是添加了註釋,擺脫了程序中從未使用的變量,並刪除了我編寫的用於測試程序的某些函數),現在突然間我的代碼似乎給了我在windows上輸出正確,在unix輸出錯誤。

請注意,我沒有做任何修改代碼的功能。

經過這麼多小時的工作,我的頭反對Seg Fault錯誤,這個最後一分鐘的錯誤將會讓所有的東西都浪費掉。當這個錯誤看似隨機出現時,我該怎麼辦?

編輯:該程序應該讀取類似於html文件的文件並打印出表格。我將每個單元格的數據加載到鏈接列表中的節點上,然後根據算法打印出信息。現在輸出在windows上正常工作,但不在unix上。我甚至不知道我需要看哪部分代碼,因爲我不知道是什麼導致了這個問題。

+15

你提供信息的方式太少,對我們是有幫助... – darioo 2010-11-03 07:13:02

+1

我不太確定你真的無意中發現了傳說中Heisenbug。請提供更多信息。代碼會很好,但編譯器警告等也可以幫助。 – 2010-11-03 07:15:54

+0

也許你可以將有問題的部分粘貼到一些上下文中......順便說一下,在機器上沒有隨機的東西:P – 2010-11-03 07:17:36

回答

5

根據您提供的信息量(無旁邊),最佳猜測是查找未初始化的變量。這將在不同的平臺上產生不同的輸出,並且是C中常見的初學者錯誤。

+0

我認爲Valgrind會是一個很好的工具,可以捕捉未初始化的變量。 – alternative 2010-11-05 22:25:12

1

首先,我們將需要您的代碼來查看發生了什麼。但是如果你描述的是真的,那麼你的代碼很可能包含了所謂的未定義行爲。未定義的行爲可能由於太多原因而發生,例如跨越數組邊界,錯誤地刪除指針等等。所以,沒有代碼什麼都不能說

+1

@「什麼都不能說」,除了剛剛說的內容非常豐富外:) – rapadura 2010-11-03 08:18:02

4

我建議你使用gdb來調試你的代碼並檢查出現分段錯誤的位置。即使你不記得做任何修改,這會給你一個很好的開始尋找的暗示。

網上有很多文檔。 這些都是基礎知識:

外殼>GDB myprogram

GDB>回溯#lists的步驟,直到分段故障發生

GDB>選擇2#你可以選擇你想要的任何一步(例如,2)

GDB>打印數量#PRINT變量破解周圍

gdb有很多功能。我想這會很快給你提示。 不要忘記下次使用版本控制系統。將代碼組織起來並清理乾淨,當然也是一種安全和不錯的方式!以避免這些可怕的事故。 (SVN或GIT足夠酷)

+0

使用調試器+1。 – 2010-11-03 08:31:55

0

隨着您提供的數據,這裏是我的解決方案。

  1. 休息一段時間,暫停問題域。
  2. 使用調試程序,逐步執行程序,確定它在哪個區域出現故障。
  3. 在段錯誤點打印數據並驗證它。

這應該解決問題。

1

通過valgrind運行它。

我可以保證你會發現你valgrind的錯誤。

如果你有權訪問unix或linux機器,那麼即使代碼正常工作,也不應該釋放你沒有通過valgrind運行的代碼。

0

編譯您的代碼並顯示所有警告。

不要隱藏警告與假冒鑄件,但認真對待並解決真正的問題。

使用不同的編譯器。在linux上,clang是一個很好的選擇,並且給出了比gcc更多的指示。

4

第1步,製作一切的副本。

將整個項目複製到某處。記下你製作該副本時的項目狀態以及日期:時間。不要編輯該副本。如果你願意,你甚至可以使文件不可寫入。你需要能夠看到你已經改變,並回到它。儘管該程序目前不能在Unix上運行,但它可以在Windows下運行,所以你知道它有一些優點,並且接近於有用的上網功能。當我在編寫程序時或在編譯器中感到不安時因爲沒有理解它(這種情況現在比10年前少得多)我傾向於失去所有我正在改變的東西,所以改變它變得困難。使用某種類型的版本控制(即使只是保留額外的副本)可以幫助你跟蹤你有什麼變化,所以當你犯了一個錯誤,你可以很容易地解決這個錯誤。差分工具,如diff在您知道如何使用它們時非常有用。對於現在你可能會想嘗試:

diff --minimal --side-by-side --ignore-all-space old_file.c new_file.c | less 

希望您使用的是支持這些選項,因爲我認爲他們可能是最有幫助的你,你有短時間內差異。如果你發現你需要在屏幕上顯示更多信息,並且你的終端窗口很大,你還可以添加--width=命令,並在終端上爲其添加一行字符。

無論如何,製作並保留大量的代碼副本,直到你知道你不再需要它們(甚至可能)。

如果您有圖形訪問,請參閱kdiff3是否可用。您可能更容易快速使用。名稱中的3表示能夠一次比較文件的3個版本(一個共同的起點和該文件的兩個編輯版本)並且很有用,但您可以稍後瞭解該文件。它完全能夠比較文件的兩個版本併產生不錯的輸出。

第2步不要忽略警告

我建議您用最高級別的警告可能你的編譯器編譯並DO忽略任何警告。如果你已經有警告而沒有告訴編譯器發出更多的警告,那麼首先檢查它們。警告是有原因的,只是偶爾會遇到應該忽略的產生警告的代碼(甚至於我通常會添加關於預期警告類型的註釋以及它爲什麼不是錯誤)。使用gcc,您可以將-Wall選項添加到編譯命令以發出所有警告。

gcc -Wall my_program.c -o my_program 

有些人可能沒有什麼意義,你,但你至少可以看看代碼,看看可能是什麼不清楚它在警戒線附近。代碼

步驟3用簡單的線條

東西,這將使警告容易理解的使用非常簡單易懂的代碼行。試圖在一行代碼中添加太多的功能使得對於該行代碼的任何警告或錯誤消息更加難以理解。

步驟4使用臨時變量

臨時變量並不一定意味着「我的程序使用更多的內存」,但他們往往意味着編譯器提供了更有意義的警告,因爲在表達式中的數據類型的變量是更清晰。

步驟5中使用的功能

這距離3層4的功能理念的延續使事情變得更容易理解。他們還會這樣做,以至於當您發現錯誤並修復錯誤時,您通常不必擔心在程序的其他位置有錯誤代碼的副本需要修復(儘管您仍然應該搜索類似的代碼確定)。

第6步斷言

有一個宏(如功能,但並不完全)呼籲assert,生活在#include <assert.h>,可以幫助你找到各種錯誤的通過使你的程序不能早於它,否則會。這聽起來很糟糕,但很多時候(特別是與分段錯誤(SIGSEGV)等與內存有關的問題)程序在它們死亡之前處於致命狀態。使用assert可幫助您將他們的死亡轉移到較早的地方,以便您可以看到他們致命的錯誤是什麼,而不僅僅是看到它的結果。

assert將布爾表達式作爲其參數 - 任何比較,整數,浮點數或指針都可以。任何你可以在ifwhile中用作條件的東西都可以。如果這個表達式爲假(0或NULL),那麼你的程序就會死在那裏,並且在很多系統上它會給你一個有用的錯誤信息,告訴你程序斷言的位置在哪裏,甚至可能是斷言是什麼。還有另一種有用的東西,這使我將在一點點談,但現在,使用斷言你只是做:

assert(x < y); 

,如果x不小於y程序將中止(實際上調用abort函數)。

這是搞什麼幫助:

int clear_buffer(char * buffer, unsigned len) 
{ /* len should be size_t but I don't want to explain that right now */ 
     assert(buffer); 
     memset(buffer, 0, len); 
} 

第7步,如果你的Unix系統有gdb然後GREAT使用調試器

。如果沒有,你可能還有其他一些調試器,你可以學習如何使用。許多Unix C編譯器把-g選項「包含調試符號」,所以添加到要傳遞到編譯器的其他選項並重新編譯程序,然後執行:

gdb ./myprogram 

,它將打印一些東西然後提示你:

(gdb) 

然後你可以設置斷點和各種好東西,但因爲你是在趕時間,讓崩潰只是做:

(gdb) r 

在通常運行程序時,在程序後面加入任何參數。 gdb將運行你的程序,直到奇怪的事情發生。奇怪的是,在這種情況下,應該是一個SIGSEGV(當你試圖訪問它不應該訪問的內存地址時,UNIX對你的程序做了什麼)。 gdb會再次提示您(gdb)。然後,你可以這樣做:

(gdb) bt 

bt代表回溯和GDB將打印出調用堆棧,這意味着被稱爲獲得當前函數的所有功能。您應該在底部附近看到main。尋找頂部附近的第一個函數,它是你寫的函數。這是您需要開始嘗試查找錯誤的地方。如果在列表頂部的功能是不是你的一個然後嘗試發行:

(gdb) up 

這將使它檢查調用堆棧上先前的功能。一旦你的功能之一說:

(gdb) list 

它會告訴你一些代碼周圍的東西是錯誤的地區。

要退出GDB你這樣做:

(gdb) quit 

而且回答Y,如果問你是否真的要退出。

如果您要使用assert並且會導致程序死機,那麼您最終不會在調用堆棧上使用相當多的庫文件來混淆您。

令人遺憾的是,3,4和5混淆了從差異獲取更好信息的能力,因此我建議試圖限制您將此編程風格添加到已出現錯誤或警告(至少現在)的地方。

我希望這有助於