Lab 1-2: Automatic Verification Environment:
assert() statement

 
實習目標 本實驗接續Lab 1-1, 在 Visual C++ 環境中熟悉使用 assert() macro 來驗證/確保 C/C++ 程式在開發過程中不斷修改或是增加功能時的正確性
 
步驟一

請下載 statisticsAndSort.exe 程式, 這個程式基本上讀取一個資料檔案raw1.dat, 預備計算檔案內所有資料的平均值, 各種資料出現的中間值, 以及出現頻率最高的數值。 在 Lab 1-1 中我們完成了一個分開在多個 .cpp 檔案裡的 程式, 但是這個程式基本上還有很多的假設與很多的漏洞:

請下載資料檔案raw2.dat 以及 raw3.dat 執行後你會發現有一些問題。 例如輸入 raw2.dat 時有一些資料好像超過範圍 (1-9), 輸入 raw3.dat 時好像資料量太多了, 或是輸入一個不存在的檔案名稱 (例如 raw4.dat) 時程式都應該會有問題。 請依照下列的步驟在程式適當的地方加入 assert() 的敘述來確保我們在撰寫程式時的一些假設:

步驟二 在 readFile() 函式中有下列敘述:
         gets(filename);
         
         fp = fopen(filename, "rt");
         fscanf(fp, "%d", dataSize);
在撰寫上述程式片段時其實有一個假設存在, 就是 "gets(filename) 所讀到使用者輸入的檔案名稱所相對應的檔案是存在的", 所以程式才沒有去偵測如果呼叫 fopen() 開啟檔案時失敗的話該如何處理。 假設我們希望在錯誤的時候直接結束程式的話, 我們可以運用 assert() 函式來確保這個假設。

我們將程式改為

         gets(filename);
         
         fp = fopen(filename, "rt");
         assert(fp != 0);
         fscanf(fp, "%d", dataSize);
        

就可以自動的驗證這個假設。 請修改你的程式加上 assert() 函式呼叫, 注意要使用 assert() 必須要加上 #include<assert.h> , assert() 函式的用法如果 不曉得的話, 請查詢 MSDN Library, 然後再試看看輸入一個錯誤的檔案名稱會發生什麼事情

通常發生 assert 錯誤時有經驗的設計者會直接按 "重試" 進入 debugger 確定到底是哪裡出錯了(以後再談)

步驟三 請在程式適當的地方加上 assert() 敘述以保證檔案中讀到的資料個數不大於陣列的大小 DATASIZE
步驟四 請在程式適當的地方加上 assert() 敘述以保證檔案中讀到的資料範圍都在 1 和 9 之間
步驟六 請在程式適當的地方加上 assert() 敘述以保證 sort() 函式所得到的結果是對的 (我們不希望每次都用眼睛檢查那些輸出來的幾百筆資料來驗證排序是否正確)。 通常我們會用別的演算法來驗證, 例如一個排序過後的陣列裡面的元素應該是由大到小的排列, 所以我們可以另外寫一個函式來驗證陣列中第 i 個元素是不是大於等於第 i+1 個元素。
步驟七 因為你在上一個步驟已經加上驗證 sort() 結果的 assert() 敘述了, 所以你現在可以輕鬆地應付兩種狀況:
  • 測試各種不同的資料組合, 你的排序程式應該都可以正確運作, 對自己實作的演算法應該可以很有信心, 演算法一開始也許沒有考慮許多特殊的資料組合, 但是在運作一段時間之後應該都可以慢慢地修補完全, 此時你也常需要把這些特殊的資料組合寫成 assert() 敘述
  • 如果有一天你的排序程式效率不敷使用, 你想要修改程式的時候, 你可以放心地去修改, 然後自動地透過 assert() 敘述驗證
現在請使用 stdlib 裡的 qsort() 函式來取代你剛才實作的 sort() 函式的功能。 下面是一個簡單的 qsort() 使用範例:
#include <stdlib.h>
int compare(const void *elem1, const void *elem2 );

void main()
{
    int a[] = {5, 3, 4, 1, 2};
    int numElements = 5;

    qsort(a, numElements, sizeof(int), compare);
    for (i=0; i<numElements; i++)
        printf("%d ", a[i]);
    printf("\n");
}

int compare(const void *elem1, const void *elem2 )
{
    if (*(int *)elem1 < *(int *)elem2)
        return -1;
    else if (*(int *)elem1 == *(int *)elem2)
        return 0;
    else
        return 1;
}
步驟八 想想看這個程式裡還有什麼地方有假設可以確認的?
  • 求出 median 值時是否有一半以上的資料大於等於它, 有一半以下的資料小於等於它,
  • 求出 freq[] 陣列後是否總和等於所有資料的總數
  • 求出 mode 值以後其數值是否大於所有其它資料值
  • ...
或是程式的輸出是不是具有什麼特性可以確認的?
步驟九 請助教檢查後, 將所完成的 project (去掉 debug/ 資料匣下的所有內容) 壓縮起來 選取 Lab1-2 上傳, 下星期的實習課程需要使用你今天所完成的程式
步驟十

請注意, assert() 是程式開發人員用的工具, 在程式開發完成交給使用者的時候, 程式裡不應該有這樣子的敘述..., 難道好不容易加進去的東西還要一列一列拿掉嗎? 不不不!!! 用下面的敘述可以讓某一個檔案裡所有的 assert() 敘述都不發生作用,

#define NDEBUG

需要在每一個檔案的開頭 (#include <assert.h> 之前) 都定義一次。 另外一種方法是使用編譯的旗標來控制, 在選單中選取

專案 / lab1-2 屬性 / 組態屬性 / C/C++ / 前置處理器 => 在前置處理器定義最後 加入

; NDEBUG


"建置 / 重建方案" 即可 (只需加入一次, 對所有的檔案都有效)

C++ 程式設計課程 首頁

製作日期: 2004/02/16 by 丁培毅 (Pei-yih Ting)
E-mail: pyting@mail.ntou.edu.tw TEL: 02 24622192x6615
海洋大學 電機資訊學院 資訊工程系 Lagoon