Windows使用者可能經常會看到類似於錯誤提示:“Error:Access violation at address 836556F8. Read of address 836556F8”。作為一個Delphi程式開發人員,遇到這種錯誤的機會比其他使用者更多(^_^)。
究竟什麼是“Access Violation”?如何在設計期避免它的出現?
Access Violation(非法訪問),General Protection Fault(一般保護性錯誤)或者Invalid Page Fault(無效分頁錯誤),雖然說法不一樣,但本質上總是由同一種錯誤引起的。Access Violation常常在電腦使用者啟動並執行程式試圖存取未被指定使用的儲存區時遇到。
Access violation at address <十六進位值>
in module <應用程式名稱>
Read of address <十六進位值>
一旦Windows要在它被分配的儲存區之外寫資料資訊,它就會覆蓋其他程式甚至作業系統的命令或資料。一旦發生了這種情況,作業系統將會癱瘓或者以某種形式關閉,你必須重新啟動電腦。例如,在Windows NT/2000下一個程式遇到這種錯誤時,Dr. Watson出現並且停止了該程式,捕獲了一些快速的細節狀態,再把它們用文本形式記錄下來。Access Violation是某些最令人氣惱的Windows程式遇到的錯誤之一。本文的目的就是讓你找到Delphi中Access Violation的解決之道。首先聲明一點,Access Violation和Microsoft Access沒有任何關係。
用Delphi開發程式時,我們可以把遇到的Access Violation分成兩大類:運行期和設計期。
一、設計期的Access Violation
1.硬體原因
在啟動或關閉Delphi IDE以及編譯一個Delphi工程時容易出現設計期的Access Violation。在你的電腦運行中出現Access Violation資訊可能由各種各樣的原因引起,包括系統BIOS、作業系統或者是硬體驅動線,有些音效卡、顯卡、網卡實際上也會導致這種錯誤。為什麼這麼說?電腦裡的每一塊卡都有它的裝置驅動程式。對於不同的製造商、不同版本的Windows或者不同版本的Delphi都可能會遇到不同的問題。如下的幾個步驟可能有助於你解決遇到的這些問題:
1. 按照必要的步驟來證實你安裝的驅動程式之間沒有衝突。
2. 有時降低顯示解析度可能會使某些古怪的顯卡驅動程式穩定一些。
3. 如果使用雙處理器的主板,則保證對每個處理器的修改步驟一樣。
4. 對於電腦上的所有硬體注意使用最新的驅動程式。
2.軟體原因
儘管Intel的電腦中Windows是最流行的作業系統,由於Windows系統天生的脆弱性和BUG,應用程式的誤操作可能導致作業系統的迅速癱瘓(有時作業系統本身也會莫名其妙的癱瘓)。選擇一個更穩定的程式開發環境是解決之道,如下幾個步驟可以協助你防止某些Access Violation的發生:
(1)儘管Windows 9X相當流行,Windows NT/2000還是從多方面被證實是一個穩定得多的環境,幾乎對於所有的Windows代碼平台而言都是這樣。
(2) 確保對於Windows NT/2000已經安裝了最新的service pack。每次安裝完新版的service pack,你會發現機器變得穩定了。
(3) 為你使用的各種版本的Delphi裝上當前的更新或補丁(BDE、ADO……),這 是提前預防錯誤的好辦法。盡量使用最新的Delphi補丁——Access Violation錯誤數量尤其是設計期的錯誤數會大大減少。
(4)如果你在IDE中經常隨機遇到Access Violation錯誤,很有可能是你安裝了一個不好的控制項、包或者一個嚮導,它不是你使用的版本的Delphi所編寫或編譯的。試著一個一個卸載定製的控制項(或者包)直到問題被解決,然後聯絡控制項廠商關注這個問題的結果。
(5) 檢查一下電腦裡是否有沒用的東西和程式衝突。奇怪的軟體程式和測試版的產品常常會導致Access Violation錯誤。
(6) 如果系統設定有錯誤,那麼Access Violation錯誤可能也會經常出現。如果你不停地遇到一個錯誤提示資訊一樣的Access Violation,記錄下這些細節,然後通知可能導致這個錯誤的軟體製造廠商。
這些就是我對設計期Access Violation錯誤的全部建議。
二、運行期的Access Violation
Delphi常見的運行期Access Violation錯誤有哪些?如何防止?
任何軟體
開發都會遇到這樣的情況:你寫好程式並測試,然後到處發送,結果使用者告訴你它失敗了。
你可能考慮用編譯指令{$D}編譯你的程式——Delphi可以建立一個有助於定位Access Violation錯誤的原始碼的鏡像檔案。工程選項對話方塊(Project|Options|Linker & Compiler)讓你指定你所需要的一切。對於單元檔案,debug資訊和單元的對象代碼一起記錄在unit檔案裡了。編譯使用這個單元的程式時,debug資訊會增加單元檔案的大小而且會增加額外的記憶體開銷,但是它不會影響最終可執行檔的大小和運行速度。包含debug資訊和鏡像檔案(Project|Options|Linker)選項的產品只有在{$D+} 編譯指令下才會完成行資訊。
Access violation通常只在程式的某一個方面表現出來。當問題第一次出現時,考慮一下使用者進行了什麼操作是很重要的,然後從這裡尋找突破口。從使用者的角度來看,你的程式中止了他們的工作,由他們來告訴你出現的問題似乎讓你延期解決這個問題了。然而,與使用者交流是你發現問題和改善程式的惟一有效方法。
現在你將可以知道在只給你衝突地址的情況下,如何輕鬆發現準確路徑、原始碼檔案、發生Access violation錯誤的行:
“Search - Find Error…”。
當一個運行期Access violation出現時,你的使用者得到的錯誤資訊類似於如下情況:
Access violation at address <十六進位值>
in module <應用程式名稱>
Read of address <十六進位值>
如果你的程式在Delphi IDE裡包含debug資訊編譯,你可以定位到導致這個錯誤原始碼這一行。
在Delphi程式中,一個最普遍導致Access Violation錯誤的原因是使用了一個沒有被建立的對象。如果第二個地址<十六進位值>是FFFFFFF或0000000,十有八九就是你訪問? 了一個沒有被建立的對象。例如,你調用了一個表單的事件,但這個表單不是自動建立的,也沒有代碼執行個體化。
?procedure TfrMain.OnCreate(Sender: TObject);
var BadForm: TBadForm;
begin
//這裡將會產生Access violation
BadForm.Refresh;
end;
假設BadForm在工程選項“Available Forms”視窗列表裡——這個視窗是需要手工建立和釋放的。在上面的代碼裡調用BadForm視窗的Refresh方法就會導致Access violation。
如果你在Debugger選項視窗使“Stop on Delphi Exceptions”生效,那麼就會彈出下面的資訊:
The message states that the EAccessViolation has occurred. The EAccessViolation is the exception class for invalid memory access errors.
這是你在設計程式時將會看到的資訊,下一個資訊框將會出現,然後程式失敗了:
Access violation at address 0043F193
in module ’Project1.exe’
Read of address 000000.
第一個十六進位數0043F193是發生Access violation的編譯代碼(Project1.exe)的運行期錯誤的地址。在IDE裡選擇功能表項目“Search|Find Error…”,在對話方塊裡輸入錯誤發生的地址(0043F193)後點擊“OK”按鈕。Delphi將會重新編譯你的工程檔案,然後顯示發生運行期錯誤的那一行代碼,這裡就是BadForm.Refresh這一行了。
下面列出了Delphi環境下導致Access violation錯誤的大部分常見原因。這個列表不是也不可能覆蓋所有可能出現的Access violation的情況。請在論壇上發送你的Access violation資訊,大家可以試著一起解決這個問題——真正的實際案例一般情況下比列出來的錯誤隱晦得多。
1. 調用一個不存在的對象
如上所述,大部分Access violation的合理原因是使用了沒有被建立或者已經被釋放的對象。為了防止這種類型的Access violation的發生,請確保你訪問的任何對象都首先被建立了。例如,當一個Table定位在一個沒有被建立的data module(從auto-crete視窗裡移走了)裡,你可能在表單的OnCreate事件裡開啟這個表。
在下面的代碼裡,在調用一個已經被刪除了的對象(b:TBitmap)事件後,一個Access violation出現了:
var b:TBitmap;
begin
b:=TBitmap.Create;
try
//對b對象進行一些操作
finally
b.free;
end;
...
//由於b已經被釋放,一個Access violation錯誤將會出現
b.Canvas.TextOut(0,0,’這是一個 Access Violation’);
end;
2. 不存在的API參數
如果你試圖給Win API函數傳遞一個不存在的參數將會出現一個Access violation錯誤。解決此類Access violation錯誤的最好方法是查閱Win API協助,看看這個API函數調用的參數資訊以及參數類型。例如,總是保證不給一個緩衝參數傳遞一個無效指標。
3. 讓Delphi釋放
當一個對象擁有另一個對象時,讓它給你做刪除工作。因為預設情況下,所有的表單(自動建立的)都屬於Application對象。當一個應用程式結束時,它釋放了Application對象,也就釋放了所有表單。例如,如果你在程式開始時自動建立了兩個表單(Form1/Unit1和Form2/Unit2),下面的代碼就會導致Access violation錯誤的出現:
unit Unit1;
...
uses unit2;
...
procedure TForm1.Call_Form2
begin
Form2.ShowModal;
Form2.Free;
//Access violation錯誤將會出現
Form2.ShowModal;
end;
4. 殺死異常
永遠不要破壞臨時異常對象(E),處理一個異常會自動釋放異常對象。如果你自己手動釋放了異常對象,程式會試圖再次釋放它,那麼就會出現Access violation錯誤:
Zero:=0;
try
dummy:= 10 / Zero;
except
on E: EZeroDivide do
MessageDlg(’不能用0做除數!’,mtError, [mbOK], 0);
E.free. ////Access violation錯誤將會出現
end;
5. 檢索一個Null 字元串
一個Null 字元串是沒有任何資料的。就是說,檢索一個Null 字元串相當於訪問一個不存在的對象,這將導致Access violation錯誤:
var s: string;
begin
s:=’’;
s[1]:=’a’;
//Access violation錯誤將會出現
end;
6. 直接引用指標
你必須間接引用指標,否則你會改變指標地址並可能會破壞其他儲存單元 :
procedure TForm1.Button1Click(Sender: TObject);
var
p1 : pointer;
p2 : pointer;
begin
GetMem(p1, 128);
GetMem(p2, 128);
//下一行導致Access violation錯誤
Move(p1, p2, 128);
//下一行方法正確
Move(p1^, p2^, 128);
FreeMem(p1, 128);
FreeMem(p2, 128);
end;
這些就是我對運行期Access Violation錯誤的全部建議,我希望你們也能對你們程式出現的Access Violation錯誤提出一些看法。
老貓點評:相信所有讀者都遇到過“Access violation” 的錯誤,如果不是自己的程式,我們有很多人就把責任都推在Bill Gates的頭上。如果你自己的程式出現了這個尷尬的錯誤,面對使用者的詢問,我們該如何解釋?本文就是最好的答案