簡體   English   中英

如何在托管代碼環境之外安全編程?

[英]How do you program safely outside of a managed code environment?

如果您是使用C或C ++編程的人,沒有內存管理的托管語言優勢,類型檢查或緩沖區溢出保護,使用指針算法,您如何確保您的程序是安全的? 您是否使用了大量的單元測試,或者您只是一個謹慎的編碼器? 你有其他方法嗎?

上述所有的。 我用:

  1. 很多謹慎
  2. 智能指針盡可能多
  3. 已經過測試的數據結構,很多標准庫
  4. 一直進行單元測試
  5. 內存驗證工具,如MemValidator和AppVerifier
  6. 每天晚上祈禱它不會在客戶現場崩潰。

實際上,我只是誇大其詞。 它不是太糟糕,如果你正確地構造你的代碼,它實際上不太難控制資源。

有趣的說明。 我有一個大型應用程序,它使用DCOM並具有托管和非托管模塊。 非托管模塊通常在開發期間更難調試,但由於在其上運行了許多測試,因此在客戶站點上執行得非常好。 托管模塊有時會遇到錯誤的代碼,因為垃圾收集器非常靈活,程序員在檢查資源使用時會變得懶惰。

我使用了大量的斷言,並構建了“調試”版本和“發布”版本。 我的調試版本運行速度遠遠低於我的發布版本,並且所做的所有檢查都是如此。

我經常在Valgrind下運行,我的代碼沒有內存泄漏。 零。 保持程序無泄漏比采取錯誤程序並修復所有泄漏要容易得多。

此外,我的代碼編譯沒有警告,盡管事實上我有編譯器設置額外的警告。 有時警告是愚蠢的,但有時他們指向一個錯誤,我修復它而不需要在調試器中找到它。

我正在編寫純C(我不能在這個項目中使用C ++),但我以非常一致的方式做C語言。 我有面向對象的類,有構造函數和析構函數; 我必須親自調用它們,但一致性有幫助。 如果我忘了打電話給一個析構函數,Valgrind會打我的頭,直到我解決它。

除了構造函數和析構函數之外,我還編寫了一個自檢函數來查看對象並確定它是否合理; 例如,如果文件句柄為空但關聯的文件數據未歸零,則表示存在某種錯誤(句柄被破壞,或文件未打開,但對象中的那些字段中有垃圾)。 此外,我的大多數對象都有一個“簽名”字段,必須設置為特定值(特定於每個不同的對象)。 使用對象的函數通常斷言對象是理智的。

每當我malloc()一些內存時,我的函數用0xDC值填充內存。 未完全初始化的結構變得顯而易見:計數太大,指針無效( 0xDCDCDCDC ),當我查看調試器中的結構時,很明顯它是未初始化的。 調用malloc()時,這比零填充內存要好得多。 (當然0xDC填充只在調試版本中;不需要發布版本來浪費那段時間。)

任何時候我釋放內存,我擦除指針。 這樣,如果我有一個愚蠢的錯誤,代碼試圖在其內存被釋放后使用指針,我立即得到一個空指針異常,這指向我正確的錯誤。 我的析構函數不接受指向對象的指針,它們接受指針指針,並在破壞對象后破壞指針。 此外,析構函數在釋放它們之前擦除它們的對象,因此如果某些代碼塊具有指針的副本並嘗試使用對象,則完整性檢查斷言會立即觸發。

Valgrind會告訴我是否有任何代碼寫下緩沖區的末尾。 如果我沒有那個,我會在緩沖區結束后放置“canary”值,並進行完整性檢查測試它們。 這些金絲雀值,如簽名值,將僅調試版本,因此發布版本不會有內存膨脹。

我有一系列單元測試,當我對代碼進行任何重大更改時,運行單元測試非常令人欣慰,並且有一些信心我沒有可怕的破壞。 當然,我在調試版本和發布版本上運行單元測試,所以我的所有斷言都有機會發現問題。

將所有這些結構放置到位需要付出額外的努力,但每天都能獲得回報。 當一個斷言觸發並指出我正確的錯誤時,我感到非常高興,而不是必須在調試器中運行該錯誤。 從長遠來看,一直保持清潔的工作要少一些。

最后,我不得不說我其實喜歡匈牙利表示法。 幾年前我在微軟工作,和喬爾一樣,我學習了匈牙利應用程序而不是破碎的變種。 它確實使錯誤的代碼看起來錯了

同樣相關 - 如何確保你的文件和套接字關閉,你的鎖被釋放,yada yada。 內存不是唯一的資源,使用GC,您本身就會失去可靠/及時的破壞。

GC和非GC都不會自動優越。 每個都有好處,每個都有它的價格,一個優秀的程序員應該能夠應付這兩個。

我在回答這個問題時也說了很多。

我已經使用C ++ 10年了。 我使用過C,Perl,Lisp,Delphi,Visual Basic 6,C#,Java和其他各種語言,這些都是我無法忘記的。

你的問題的答案很簡單: 你必須知道你在做什么 ,而不是C#/ Java。 產生傑夫阿特伍德關於“Java學校”的咆哮的原因不止於此。

從某種意義上說,你的大部分問題都是荒謬的。 你提出的“問題”只是硬件真正運作的事實。 我想挑戰你用VHDL / Verilog編寫一個CPU和RAM,看看它們是如何工作的,即使真的很簡單。 您將開始意識到C#/ Java方式是對硬件的抽象描述。

更容易的挑戰是從初始上電開始為嵌入式系統編程基本操作系統; 它會告訴你你需要知道什么。

(我也寫過C#和Java)

我們用C語言編寫嵌入式系統。 除了使用任何編程語言或環境中常見的一些技術外,我們還使用:

  • 靜態分析工具(例如PC-Lint )。
  • 符合MISRA-C (由靜態分析工具強制執行)。
  • 根本沒有動態內存分配。

安德魯的答案很好,但我也會在列表中加入紀律。 我發現經過足夠的C ++練習之后,你會對安全性感到非常好的感覺,以及為快速消遣者提供什么乞求。 你傾向於開發一種在遵循安全實踐時感覺舒適的編碼風格,並且如果你試圖將智能指針強制轉換回原始指針並將其傳遞給其他東西,那么你會感覺到這種風格。

我喜歡把它想象成一個商店里的電動工具。 一旦你學會正確使用它,只要你確保始終遵守所有安全規則,它就足夠安全了。 當你認為你可以放棄你受傷的安全護目鏡時。

我已經完成了C ++和C#,我沒有看到關於托管代碼的所有宣傳。

哦,對,內存有一個垃圾收集器,這很有用......除非你當然不使用C ++中的普通舊指針,如果你只使用smart_pointers,那么你沒有那么多問題。

但后來我想知道......你的垃圾收集器是否可以保護你:

  • 保持數據庫連接打開?
  • 保持鎖定文件?
  • ...

資源管理比內存管理要多得多。 好處是C ++是你快速學習資源管理和RAII的含義,以便它成為一種反射:

  • 如果我想要一個指針,我想要一個auto_ptr,一個shared_ptr或一個weak_ptr
  • 如果我想要數據庫連接,我想要一個對象'連接'
  • 如果我打開一個文件,我想要一個對象'文件'
  • ...

至於緩沖區溢出,好吧,它不像我們在任何地方使用char *和size_t。 我們確實有一些東西叫做'string','iostream',當然還有已經提到過的vector :: at方法讓我們擺脫了這些限制。

經測試的庫(stl,boost)很好,使用它們並繼續解決更多功能問題。

除了這里給出的很多好的提示,我最重要的工具是DRY - 不要重復自己。 我不會在我的代碼庫中傳播容易出錯的代碼(例如,使用malloc()和free()來處理內存分配)。 我的代碼中只有一個位置,其中調用了malloc和free。 它位於包裝器函數MemoryAlloc和MemoryFree中。

所有參數檢查和初始錯誤處理通常作為圍繞調用malloc的重復樣板代碼給出。 此外,它允許任何需要僅修改一個位置的東西,從簡單的調試檢查開始,例如計算成功調用malloc和free,並在程序終止時驗證兩個數字是否相等,直到各種擴展安全檢查。

有時候,當我在這里讀到一個問題時,“我總是要確保strncpy終止字符串,還有其他選擇嗎?”

strncpy(dst, src, n);
dst[n-1] = '\0';

接下來是幾天的討論,我總是想知道將重復功能提取到函數中的藝術是否是一種失去的高級編程藝術,這種藝術不再在編程講座中講授。

char *my_strncpy (dst, src, n)
{
    assert((dst != NULL) && (src != NULL) && (n > 0));
    strncpy(dst, src, n);
    dst[n-1] = '\0';
    return dst;
}

解決了代碼重復的主要問題 - 現在讓我們考慮一下strncpy是否適合這項工作。 性能? 過早優化! 它被證明是瓶頸之后的一個單一位置。

C ++具有您提到的所有功能。

有內存管理。 您可以使用智能指針進行非常精確的控制。 或者有幾個垃圾收集器可用,雖然它們不是標准的一部分(但大多數情況下智能指針綽綽有余)。

C ++是一種強類型語言。 就像C#一樣。

我們正在使用緩沖區。 您可以選擇使用界面檢查版本的界面。 但是,如果您知道沒有問題,那么您可以自由使用未經檢查的界面版本。

將()(已檢查)的方法與運算符[](未選中)進行比較。

是的,我們使用單元測試。 就像你應該在C#中使用一樣。

是的,我們是謹慎的程序員。 就像你應該在C#中一樣。 唯一的區別是兩種語言的陷阱不同。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM