3.2.4 函數內變數跟蹤


    PC-Lint的函數值跟蹤功能會跟蹤那些將要傳遞給函數(作為函數參數)變數值,當發生函數調用時,這些值被用來初始化函數參數。這種跟蹤功能被用來測定返回值,記錄額外的函數調用,當然還可以用來偵測錯誤。考察下面的例子程式碼:
t1.cpp:
1 int f(int);
2 int g()
3 { return f(0); }
4 int f( int n )
5 { return 10 / n; }
在這個例子中,f()被調用的時候使用0作為參數,這將導致原本沒有問題的10/n語句產生被0除錯誤,使用命令lin -u t1.cpp可以得到以下輸出:
--- Module: t1.cpp
During Specific Walk:
File t1.cpp line 3: f(0)
t1.cpp 5 Warning 414: Possible division by 0 [Reference:File t1.cpp: line 3]
第一個注意到的事情是短語“During Specific Walk”,緊接著是函數調用發生的位置,函數名稱以及參數,再下來就是錯誤資訊。如果錯誤資訊中缺少了錯誤再現時的錯誤行和用來標記錯誤位置的指示信 息,這是因為檢查到錯誤的時候程式碼(被調用函數的程式碼)已經走過了。如果像下面一樣調換一下兩個函數的位置:
t2.cpp:
1 int f( int n )
2 { return 10 / n; }
3 int g()
4 { return f(0); }
這種情況下就不會出現被0除的警告,因為此時f(0)在第四行,函數f()的程式碼已經過了,在這種情況下就需要引入multi-pass選項。如果在剛才的例子中使用lin -u -passes(2) t2.cpp命令,那麼輸出就變成:
--- Module: t2.cpp
/// Start of Pass 2 ///
--- Module: t2.cpp
During Specific Walk:
File t2.cpp line 4: f(0)
t2.cpp 2 Warning 414: Possible division by 0 [Reference:File t2.cpp: line 4]


使用-passes(2)選項將會檢查程式碼兩遍,一些作業系統 支援在命令行中使用-passes(2),對於這樣的系統,可以使用-passes=2 -passes[2]代替。通過冗長的資訊可以看出來,以pass 2開始表示第一次檢查沒有產生警告資訊。這一次得到的錯誤資訊和前一次不同,在某種情況下我們可以推斷出指定函數調用的返回值,至少可以得到一些返回值的 屬性。以下麵的模組為例:
t3.cpp:
1 int f( int n )
2 { return n - 1; }
3 int g( int n )
4 { return n / f(1); }
使用命令 lin -u -passes(2) t3.cpp,可以得到以下輸出資訊:
--- Module: t3.cpp
/// Start of Pass 2 ///
--- Module: t3.cpp


{ return n / f(1); }
t3.cpp 4 Warning 414: Possible division by 0 [Reference:File t3.cpp: lines 2, 4]


第一遍檢查我們知道調用函數f()傳遞的參數是1,第二遍檢查先處理了函數f(),我們推斷出這個參數將導致返回結果是0,當第二遍檢查開始處理函 g()的時候,產生了被0除錯誤。應該注意到這個資訊並不是在短語“During Specific Walk”之前出現的,這是因為錯誤是在對函數g()進行正常的處理過程中檢測到的,此時並沒有使用為函數g()的參數指定的值。指定的函數調用能夠產生 附加的函數調用,如果我們pass足夠多的檢測次數,這個過程可能會重複發生,參考下面的程式碼:
t4.cpp:
1 int f(int);
2 int g( int n )
3 { return f(2); }
4 int f( int n )
5 { return n / f(n - 1); }
第五行的分母f(n-1)並不會引起懷疑,直到我們意識到f(2)調用將導致f(1)調用,最終會調用f(0),迫使最終的返回值是0。使用下面的命令行:
lin -u -passes(3) t4.cpp
輸出結果如下:
--- Module: t4.cpp
{ return f(2); }
t4.cpp 3 Info 715: Symbol 'n' (line 2) not referenced
/// Start of Pass 2 ///
--- Module: t4.cpp
/// Start of Pass 3 ///
--- Module: t4.cpp
During Specific Walk:
File t4.cpp line 3: f(2)
File t4.cpp line 5: f(1)
t4.cpp 5 Warning 414: Possible division by 0 [Reference:File t4.cpp: lines 3, 5]
到這裏已經處理了三遍才檢測到可能的被0除錯誤,想瞭解為什麼需要處理三遍可以看看這個選項-specific_wlimit(n)。需要注意的是,指定的調用序列,f(2)f(2),是作為警告資訊的序言出現的。


3.3 賦值順序檢查


    當一個運算式的值依賴於賦值的順序的時候,會產生警告564。這是C/C++語言中非常普遍的一個問題,但是很少有編譯器會分析這種情況。比如
n++ + n
這個語句是有歧義的,當左邊的+操作先執行的話,它的值會比右邊的先執行的值大一,更普遍的例子是這樣的:
a[i] = i++;
f( i++, n + i );
一個例子,看起來好像自加操作應該在陣列索引計算以後執行,但是如果右邊的賦值操作是在左邊賦值操作之前執行的話,那麼自加一操作就會在陣列索引計算之前 執行。雖然,賦值操作看起來應該指明一種操作順序,但實際上是沒有的。第二個例子是有歧義的,是因為函數的參數值的計算順序也是沒有保證的。能保證賦值順 序的操作符是布林與(&&)或(||)和條件賦值(? :)以及逗號(,),因此:
if( (n = f()) && n > 10 ) ...
這條語句是正確的,而:
if( (n = f()) & n > 10 ) ...
將產生一條警告。


3.4 弱定義檢查


    這裏的弱定義包含是以下內容:巨集定義、typedef名字、聲明、結構、聯合和枚舉類型。因為這些東西可能在模組中被過多定義且不被使用,PC-Lint 有很多消息用來檢查這些問題。PC-Lint的消息749-769 1749-1769都是保留用來作為弱定義提示的。
 (1) 當一個檔#include的頭檔中沒有任何引用被該檔使用,PC-Lint會發出766警告。
 (2) 為了避免一個頭檔變得過於大而臃腫,防止其中存在冗餘的聲明,當一個頭檔中的物件聲明沒有被外部模組引用到時,PC-Lint會發出759警告。
 (3) 當變數或者函數只在模組內部使用的時候,PC-Lint會產生765警告,來提示該變數或者函數應該被聲明為static
    如果你想用PC-Lint檢查以前沒有檢查過的程式碼,你可能更想將這些警告資訊關閉,當然,如果你只想查看頭檔的異常,可以試試這個命令:
lint -w1 +e749 +e?75? +e?76? ...


(未完,待續......


3.5 格式檢查


    PC-Lint會檢查printfscanf(及其家族)中的格式衝突,例如:
printf( "%+c", ... )
產生566警告,因為加號只在數字轉換時有用,有超過一百個這樣的組合會產生警告,編譯器通常不標記這些矛盾,其他的警告還有對壞的格式的抱怨,它們是 557567。我們遵循ANSI C建立的規則,可能更重要的是我們還對大小不正確的格式進行標記(包括警告558, 559, 560 561)。比如 %d 格式,允許使用intunsigned int,但是不支援doublelong(如果longint長),同樣,scanf需要參數指向的物件大小正確。如果只是參數的類型(不是大小)與 格式不一致,那將產生626627警告。-printf -scanf選項允許用戶指定與printfscanf函數族類似的函數,-printf_code -scanf_code也可以被用來描述非標準的 % 碼。


3.6 縮進檢查


    根據程式碼中的縮進問題,PC-Lint也會產生相應的警告,因為縮進的問題有很大一部分是由於程式碼結構不良或者大括弧的遺漏造成的。比如下面的例子:
if( ... )
if( ... )
statement
else statement
明顯這裏的else是和第一個if語句對應的,而在這裏編譯器則把它和第二個if對應起來。PC-Lint會對這種情況產生警告。和這樣的縮進檢查相關的 警告主要有三個725(no positive indentation)525(negatively indented from)539Did not expect positive indentation from Location)要進行縮進檢查,我們首先要設置檔中的tab鍵所對應的空格數,默認的是佔用8個空格,這個參數可以用-t#選項進行修改。比如 -t4表示tab鍵佔用4個空格長度。另外,縮進檢查還和程式碼的編碼格式策略相關,需要進行必要的調整。


3.7 const變數檢查


    對於const變數的檢查,PC-Lint是完全支持的。使用const變數,對於提高程式碼的品質非常有好處,看一下下面的例子:
char *strcpy( char *, const char * );
const char c = 'a';
const char *p = &c;
void main()
{
char buf[100];
c = 'b';
*p = 'c';
strcpy( p, buf );
...
裏的c*P指向的內容都是靜態變數,不可修改。上面的程式碼明顯違反了這個規定,會產生Error(11),另外,把P作為第一個參數傳入strcpy 中,會產生警告605Increase in pointer capability),而把buf作為第二個參數傳入strcpy函數中,會產生警告603Symbol 'Symbol' (Location) not initialized),因為buf沒有初始化,而作為靜態變數的第二個參數,是不能在strcpy函數中再被初始化的。


3.8 volatile變數檢查


    對於volatile變數的檢查,在PC-Lint中有這樣的規定,如果一個運算式中同時使用了兩次相同的volatile變數,那麼就會給出564警告,因為這時候會產生賦值順序的問題。
volatile char *p;
volatile char f();
n = (f() << 8) | f(); /* Warning 564 */
n = (*p << 8) | *p; /* Warning 564 */

    全站熱搜

    立你斯 發表在 痞客邦 留言(0) 人氣()