Showing posts with label Programming. Show all posts
Showing posts with label Programming. Show all posts

Thursday, April 14, 2011

Googlemock: Mock Object 應用於 C++ RAII 實例

在 Unit Test 中,Mocking 的技巧是用假的元件去取代受測元件所依賴的外部元件,稱為 Mock Object。測試者藉由控制 Mock Object 去可以改變受測元件的內部流程、或是驗證受測元件與外部元件的互動行為。

在 C++ 當中,常常慣用 RAII 的技巧,讓 Object 在進入 Function/Block 的範圍內獲取資源,離開範圍內釋放資源。舉例來說:
void example()
{
  File file("output.log");
  file.write("test");
}
在這種情況下,通常都會在測試程式中另外實作一組假的 File。不過,因為 Object 生成的時機是在進入受測的函式中,沒有機會用 ON_CALL 去控制 Mock Object 的行為。

所幸,你可以用下面的方式將 File 的實作,交給另外一個擁有相同介面的類別。這麼一來,我們還是可以控制這個新的類別。
// MockFile 是真正使用 Googlemock 的類別
class MockFile
{
public:
  MOCK_METHOD0(write, void(
    char *szLine));
};

MockFile *g_pMockFile = NULL;

// 將 File::write 導到 MockFile 上
File::write(char *szLine) 
{ 
  g_pMockFile->write(szLine); 
};

TEST_F(TestSuite, test_file_write)
{
  MockFile mockFile;
  EXPECT_CALL(mockFile, write(_));

  // 設定給 File::write() 使用這個 Mock Object
  g_pMockFile = &mockFile;

  example();
}
如此一來,就可以使用 Googlemock 幫我們取代掉 RAII 技巧所使用的類別。

Friday, April 01, 2011

Win32程式設計: _beginthreadex or CreateThread?

先說結論,永遠先用_beginthreadex()來建立新的執行續,把它當作是你的 Code Convention。

為什麼?先從動機說起。

在 C Run-Time (CRT) 函式庫裡有許多函式在多執行續的環境下,會利用 Thread Local Storage (TLS) 去存取每個獨立執行續的資源,藉此達到 Thread-Safe。

舉例來說,errno 是檢查錯誤碼的變數,在多執行續的環境下,如果A()和B()兩個函式從不同的執行續改寫了 errno,那麼接下來的程式碼就無法確認 errno 來決定上面一個函式究竟是成功還是失敗。

為了解決這種問題,在多執行續的 CRT 下,errno 會被替換成 #define errno (*_errno())。_errno() 裡面做的事情就是去取出 TLS 裡面存放的 per-thread errno。這樣每一個執行續都有自己的 errno,不用擔心內容不正確的問題。

像是 errno 這樣的應用還有很多,它們都是存放在 _tiddata 這個結構中。_tiddata 又是放在 TLS 裡面。

再回來看_beginthreadex(),它比 CreateThread() 是多做了一些事情。最主要就是多初始一份 _tiddata 並且放在新的執行續的 TLS 中,讓 CRT 的函式可以使用這些 _tiddata 來達成 Thread-safe。

你可能會問,那...我的產品都已經釋出了,而且都是用CreateThread(),怎麼辦?先別擔心,先讓我們來看沒有正確使用 _beginthreadex() 會造成什麼影響。

  • _tiddata 沒有初始化?
    如果沒有初始化,在 CRT 的函式中只要是第一個使用到 _tiddata 的地方,還是會負責建立一份的。舉例來說,_errno() 裡面檢查到沒有 _tiddata,它會自己建立一份,再放回 TLS 中。所以你的 CRT 函式還是會正確運作的。
  • 記憶體洩漏?
    正常來說,_tiddata 只會在 _endthreadex() 呼叫之後才會被刪除掉。_beginthreadex() 產生出來的執行續在結束後會自動呼叫到 _endthreadex()。所以理論上 CreateThread() 產生的執行續就會有記憶體洩漏的問題。但是這點,Microsoft的工程師也幫你想到了,不管你是 Static-link 或是 Dynamic-link 多執行續的 CRT,CRT 會在收到 DLL_THREAD_DETACH 的時候再幫你檢查 _tiddata 然後把它刪除掉。

    不過這邊 Static-link 可能會有個問題。舉例來說,A.exe 裡面用了 errno 然後當下 malloc 了一份 _tiddata,然後 call-stack 進入到 B.dll,之後 B.dll 返回離開 call-stack 就會收到 DLL_THREAD_DETAH,B.dll 就會刪除這個 _tiddata,可是 A.exe 和 B.dll 是分別使用獨立的 Heap,這樣一刪除下去,恐怕會有 Heap Corruption 的機會。(純粹推理,還沒驗證過)
  • 還有其他的問題嗎?
    _beginthreadex() 還會呼叫 _fpclear() 和支援.NET AppDomain,前者和浮點數相關函數初始化有關,後者會將新的執行續在 Caller 相同的 AppDomain 上執行。
    (反組譯了一下我電腦上的 msvcr80.dll,_fpclear()其實什麼都沒做就返回了。)

如果是用 Multithread Dynamic-Link C Run-Time 看起來就沒什麼問題囉。(謎之聲:那個在 Production Code 用了 CreateThread 的人是誰呢? XD)


參考資料:

Thursday, March 24, 2011

User-Buffered I/O for C Library

昨天討論到了 Win32 Kernel Mode 下的 Buffered I/O 和 Unbuffered I/O 的差異。其實在 User Mode 的環境下,C標準函式庫的File I/O(fread, fwrite...)也存在自己的 Buffered 方式,稱之為 User-Buffered I/O 或稱為 Stream Buffering。重點在於決定何時把 Buffer 內的東西透過 System Call 倒入 Kernel Mode。

  • Fully Buffered: 
    • 系統或 Caller 決定一個固定大小的緩衝區。
    • 當透過 File I/O 寫滿緩衝區,I/O 才會發生。
  • Line Buffered: 
    • 當 File I/O 寫到換行符號,I/O才會發生。
    • 在 Win32,沒有這個選項...
  • No Buffered:
    • 就是沒有Buffer...
參考資料:

Tuesday, March 22, 2011

C++:auto_ptr, scoped_ptr, shared_ptr and weak_ptr

簡單整理各種Smart Pointer的特色:
  • auto_ptr
    • Supported in C++ Standard Library
    • Support Ownership Transfer
  • scoped_ptr
    • Supported in C++ TR1
    • Not Support Ownership Transfer
  • shared_ptr
    • Supported in C++ TR1
    • Using Reference Count
    • Object copy or assignment will increase the reference count.
  • weak_ptr
    • Supported in C++ TR1
    • Created from shared_ptr. Cannot be created alone.
    • Support Ownership Transfer
    • Easy and safe to check a pointer valid or not, without increasing the reference count.
延伸閱讀:

Friday, March 11, 2011

Security Enhancement in CRT: Good or Bad Side-Effect?

從Visual Studio 2005開始,編譯器都會建議使用者把某些CRT function換成更安全的版本。舉例來說,編譯器會建議你把strcpy改成strcpy_s。

這樣改的好處是什麼呢?在程式執行的過程中,有一種類型的bug叫做buffer overflow。以strcpy來說,就是你的來源字串長度可能比準備的buffer來的長,又因為strcpy沒有buffer size的資訊,所以在資料複製的過程中,就有可能覆蓋到buffer後面的資料。

如果buffer在stack上,那就有可能覆蓋到其他變數,甚至是call stack的return address。總之,小則程式執行不正常或崩潰,大則會被attacker利用來取得控制權。

換成安全的版本,編譯器就會嘗試把buffer size偷偷帶進strcpy_s當中,幫你在執行的過程中檢查有沒有buffer overflow。如果有,就會呼叫invalid parameter handler。

看到這裡,你可能就直接把全部的strcpy換成strcpy_s。

等等,預設的invalid parameter handler會丟出一個Exception,如果沒有exception handler,預設會執行UnhandledExceptionHandler()。講白話一點,就是會造成程式的崩潰。那使用者會看到什麼呢?答案是一個程式執行無效之類的對話框。

嘿,如果這是個文件編輯程式,使用者編輯到一半的文件就這樣不見了,不氣的跳腳才奇怪。假設樂觀的工程師如你,也是會考慮使用者經驗,又專注完美近乎科科吧!XD

要避免字串複製的buffer overflow,你還可以選擇用strncpy。如果buffer比較小,頂多就是來源字串塞不進去,之後你還可以做一些比較友善的處理。(例如跳出一個切腹道歉的視窗,幫使用者自動儲存文件,之後再crash。噗~)

另外一個方式也沒忘記,你可以覆蓋預設的invalid parameter handler,指到自製的例外處理函式。如果你想要集中處理的話,這樣可以讓所有的安全版的CRT function全都使用同一個handler。

結論:多想兩分鐘,你其實也可以有另一個選擇。XD