PC-lint

PC-lint

C/C++語言的語法擁有其它語言所沒有的靈活性,這種靈活性帶來了代碼效率的提升,但相應也使得代碼編寫具有很大的隨意性,另外C/C++編譯器不進行強制類型檢查,也不做任何邊界檢查,這就增加了代碼中存在隱患的可能性。

基本介紹

  • 中文名:PC-lint
  • 外文名:C/C++語言
  • 擁有靈活性
  • 不進行:強制類型檢查
引言,介紹,功能,強類型,變數跟蹤,順序檢查,弱定義,格式檢查,縮進檢查,使用方法,安裝配置,開發工具,特殊情況,總結,附錄一,附錄二,附錄三,

引言

如果能夠在代碼提交測試之前發現這些潛在的錯誤,就能夠極大地減輕測試人員的壓力,減少軟體項目的除錯成本,可是傳統的C/C++編譯器對此已經無能為力,這個任務只能由專用的代碼檢查工具完成。有很多C/C++靜態代碼檢查工具,其中Logiscope RuleChecker和PC-Lint 是套用比較廣泛的兩個工具。本文將介紹如何安裝和配置PC-Lint代碼檢查工具以及將PC-Lint 與常見的代碼編輯軟體,如Visual C++,Source Insight集成的方法,同時還將簡要介紹一些PC-Lint常用的代碼檢查選項。

介紹

PC-Lint 是GIMPEL SOFTWARE公司開發的C/C++軟體代碼靜態分析工具,它的全稱是PC-Lint/FlexeLint for C/C++,PC-Lint 能夠在Windows、MS-DOS和OS/2平台上使用,以二進制執行檔的形式發布,而FlexeLint 運行於其它平台,以原始碼的形式發布。PC-lint 在全球擁有廣泛的客戶群,許多大型的軟體開發組織都把PC-Lint 檢查作為代碼走查的第一道工序。PC-Lint不僅能夠對程式進行全局分析,識別沒有被適當檢驗的數組下標,報告未被初始化的變數,警告使用空指針以及冗餘的代碼,還能夠有效地幫你提出許多程式在空間利用、運行效率上的改進點。
通過下面的例子就可以看出PC-Lint 工具的強大功能:
1:
2:char *report( int m, int n, char *p )
3:{
4:int result;
5:char *temp;
6:long nm;
7:int i, k, kk;
8:char name[11] = "Joe Jakeson";
9 :
10:nm = n * m;
11:temp = p == "" ? "null" : p;
12:for( i = 0; i<m; I++ ) {
14:k++;
15:kk = i;
16:}
17:
18:if( k== 1 ) result = nm;
19:else if( kk > 0 ) result = 1;
20:else if( kk < 0 ) result = -1;
21:
22:if( m == result ) return( temp );
23:else return( name );
24:}
這是一段C 代碼,可以通過大多數常見的C 語言編譯器的檢查,但是PC-Lint能夠發現其中的錯誤和潛在的問題:第8行向name數組賦值時丟掉了結尾的nul 字元,第10行的乘法精度會失準,即使考慮到long 比int 的字長更長,由於符號位的原因仍然會造成精度失準,第11行的比較有問題,第14行的變數k沒有初始化,第15行的kk可能沒有被初始化,第22行的result 也有可能沒有被初始化,第23行返回的是一個局部對象的地址。
隨著C++語言的出現,C/C++編譯器有了更嚴格的語法檢查,但是仍然不能避免出現有BUG的程式。C++的類型檢查依然不如Pascal那么嚴格。對於一個小程式,多數程式設計師都能夠及時發現上面出現的錯誤,但是從一個擁有成千上萬行代碼的大型軟體中找出這些瑕疵將是一項煩瑣的工作,而且沒有人可以保證能找出所有的這類問題。如果使用PC-Lint,只需通過一次簡單的編譯就可以檢查出這些錯誤,這將節省了大量的開發時間。從某種意義上說。PC-Lint 是一種更加嚴格的編譯器,它除了可以檢查出一般的語法錯誤外,還可以檢查出那些雖然符合語法要求,但很可能是潛在的、不易發現的錯誤。

功能

PC-Lint 能夠檢查出很多語法錯誤和語法上正確的邏輯錯誤,PC-Lint 為大部分錯誤訊息都分配了一個錯誤號,編號小於1000的錯誤號是分配給C 語言的,編號大於1000的錯誤號則用來說明C++的錯誤訊息。 表1 列出了PC-Lint 告警訊息的詳細分類:
錯誤說明
C
C++
告警級別
語法錯誤
1-199
1001-1199
1
內部錯誤
200-299
0
致命錯誤
300-399
0
告警
400-699
1400-1699
2
訊息
700-899
1700-1899
3
可選信息
900-999
1900-1999
4
以C語言為例,其中的編號1-199指的是一般編譯器也會產生的語法錯誤;編號200-299是PC-Lint 程式內部的錯誤,這類錯誤不會出現在代碼中的;編號300-399指的是由於記憶體限制等導致的系統致命錯誤。編號400-999中出現的提示信息,是根據隱藏代碼問題的可能性進行分類的:其中編號400-699 指的是被檢查代碼中很可能存在問題而產生的告警信息;編號700-899中出現的信息,產生錯誤的可能性相比告警信息來說級別要低,但仍然可能是因為代碼問題導致的問題。編號900-999 是可選信息,他們不會被默認檢查,除非你在選項中指定檢查他們。
PC-Lint/FelexLint 提供了和許多編譯器類似的告警級別設定選項-wLevel,它的告警級別分為以下幾個級別,預設告警級別為3級:
-w0 不產生信息(除了遇到致命的錯誤)
-w1 只生成錯誤信息-- 沒有告警信息和其它提示信息
-w2 只有錯誤和告警信息
-w3 生成錯誤、告警和其它提示信息(這是默認設定)
-w4 生成所有信息
PC-Lint/FelexLint還提供了用於處理函式館的頭檔案的告警級別設定選項-wlib(Level),這個選項不會影響處理C/C++原始碼模組的告警級別。它有和-wLevel 相同的告警級別,預設告警級別為3級:
-wlib(0) 不生成任何庫信息
-wlib(1) 只生成錯誤信息(當處理庫的原始碼時)
-wlib(2) 生成錯誤和告警信息
-wlib(3) 生成錯誤、告警和其它信息(這是默認設定)
-wlib(4) 產生所有信息
PC-Lint 的檢查分很多種類,有強類型檢查、變數值跟蹤、語義信息、賦值順序檢查、弱定義檢查、格式檢查、縮進檢查、const 變數檢查和volatile變數檢查等等。對每一種檢查類型,PC-Lint 都有很多詳細的選項,用以控制PC-Lint的檢查效果。PC-Lint的選項有300多種,這些選項可以放在注釋中(以注釋的形式插入代碼中),例如:
/*lint option1 option2 ... optional commentary */ 選項可以有多行
//lint option1 option2 ... optional commentary 選項僅為一行(適用於C++)
選項間要以空格分開,lint 命令一定要小寫,並且緊跟在/*或//後面,不能有空格。如果選項由類似於操作符和運算元的部分組成,例如-esym(534, printf, scanf, operator new),其中最後一個選項是operator new,那么在operator和new 中間只能有一個空格。PC-Lint 的選項還可以放在宏定義中,當宏被展開時選項才生效。例如:
#define DIVZERO(x) /*lint -save -e54 */ ((x) /0) /*lint -restore */ 允許除數為0而不告警
下面將分別介紹PC-Lint 常用的,也是比較重要的代碼檢查類型,並舉例介紹了各個檢查類型下可能出現的告警信息以及常用選項的用法:

強類型

強類型檢查選項“-strong”和它的輔助(補充)選項“-index”可以對typedef定義的數據類型進行強類型檢查,以保證只有相同類型之間的變數才能互相賦值,強類型檢查選項strong的用法是:
-strong( flags[, name] ... )
strong選項必須在typedef定義類型之前打開,否則PC-Lint就不能識別typedef定義的數據類型,類型檢查就會失效。flags參數可以是A、J、X、B、b、l 和f,相應的解釋和弱化字元在表2 中列出:
表2 強類型檢查strong 選項和參數表
A
對強類型變數賦值時進行類型檢查,這些賦值語句包括:直接賦值、返回值、參數傳遞、初始化。
A 後面可以跟以下字元,用來弱化A的檢查強度:
i 忽略初始化
r 忽略Return 語句
p 忽略參數傳遞
a 忽略賦值操作
c 忽略將常量賦值(包括整數常量、常量字元串等)
z 忽略Zero 賦值,Zero 定義為任何非強制轉換為強類型的0 常量。
例如:0L 和(int)0 都是Zero,但是(HANDLE)0 當HANDLE 是一個強類型的時候就不是Zero。(HANDLE *)0 也不是例如使用-strong(Ai,BITS)設定,PC-Lint 將會對從非BITS類型數據向BITS 類型數據賦值的代碼發出告警,但是忽略變數初始化時的此類賦值。
X
當把強類型的變數賦指給其他變數的時候進行類型檢查。弱化參數i, r, p, a, c, z 同樣適用於X 並起相同的作用
J
選項是當強類型與其它類型進行如下的二進制操作時進行檢查,下面是J 的參數:
e 忽略==、!=和?:操作符
r 忽略>、>=、<和<=
o 忽略+、-、*、/、%、|、&和^
c 忽略該強類型與常量進行以上操作時的檢查
z 忽略該強類型與Zero 進行以上操作時的檢查
使用忽略意味著不會產生告警信息。舉個例子,如果Meters 是個強類型,那么它只在判斷相等和其他關係操作時才會被正確地檢查,其它情況則不檢查,在這個例子中使用J 選項是正確的。
B
B 選項有兩個效果:
1. 出於強類型檢查的目的,假設所有的Boolean 操作返回一個和Type 兼容的類型,所謂Boolean 操作就是那些指示結果為true 或false 的操作,包括前面提到的四種關係運算符和兩種等於判斷符,取反操作符!,二元操作符&&和||。
2. 在所有需要判斷Bolean 值的地方,如if語句和while 語句,都要檢查結果是否符合這個強類型,否則告警。
例如if(a)...當a 為int 時,將產生告警,因為int與Bolean類不兼容,所以必須改為if(a != 0)。
b
僅僅假定每一個Bolean 類操作符都將返回一個與Type 類型兼容的返回值。與B 選項相比,b 選項的限制比較寬鬆。
l
庫標誌,當強類型的值作為參數傳遞給庫函式等情況下,不產生告警。
f
與B或b 連用,表示抑止對1bit 長度的位域是Boolean 類型的假定,如果不選該項表示1bit 長度的位域被預設假定為Boolean 類型。
這些選項字元的順序對功能沒有影響。但是A和J選項的弱化字元必須緊跟在它們之後。B項和b 選項不能同時使用,f選項必須搭配B選項或b 選項使用,如果不指定這些選項,-strong 的作僅僅聲明type為強類型而不作任何檢查。下面用一段代碼演示-strong 選項的法:
//lint -strong(Ab,Bool) <選項是以注釋的形式插入代碼中>
typedef int Bool;
Bool gt(int a, b)
{
if(a) return a > b; // OK
else return 0; // Warning
}
例子代碼中Bool 被聲明成強類型,如果沒有指定b 選項,第一個return 語句中的比較操作就會被認為與函式類型不匹配。第二個return 語句導致告警是因為0不是各Bool 類型,如果添加c選項,例如-strong(Acb,Bool),這個告警就會被抑制。再看一個例子:
/*lint -strong( AJXl, STRING ) */
typedef char *STRING;
STRING s;
...
s = malloc(20);
strcpy( s, "abc" );
由於malloc和strcpy是庫函式,將malloc的返回值賦給強類型變數s或將強類型變數s傳遞給strcpy時會產生強類型衝突,不過l 選項抑制了這個告警。強類型也可用於位域,出於強類型檢查的目的,先假定位域中最長的一個欄位是優勢Boolean 類型,如果沒有優勢Boolean 或位域中沒有哪個欄位比其它欄位長,這個類型從位域被切開的位置開始成為“散”類型,例如:
//lint -strong( AJXb, Bool )
//lint -strong( AJX, BitField )
typedef int Bool;
typedef unsigned BitField;
struct foo
{
unsigned a:1, b:2;
BitField c:1, d:2, e:3;
} x;
void f()
{
x.a = (Bool) 1; // OK
x.b = (Bool) 0; // strong type violation
x.a = 0; // strong type violation
x.b = 2; // OK
x.c = x.a; // OK
118
x.e = 1; // strong type violation
x.e = x.d; // OK
} 上面例子中,成員a和c是強類型Bool,成員d 和e是BitField 類型,b不是強類型。為了避免將只有一位的位域假設成Boolean類型, 需要在聲明Boolean的-strong 中使用f選項,上面的例子就應該改成這樣:-strong(AJXbf,Bool)。
另一個強類型檢查選項是index,index的用法是:
-index( flags, ixtype, sitype [, sitype] ... )
這個選項是對strong選項的補充,它可以和strong選項一起使用。這個選項指定ixtype是一個排除索引類型,它可以和Strongly Indexed 類型sitype的數組(或指針)一起使用,ixtype和sitype被假設是使用typedef聲明的類型名稱。flags可以是c或d,c允許將ixtype和常量作為索引使用,而d 允許在不使用ixtype的情況下指定數組的長度(Dimensions)。下面是一個使用index的例子:
//lint -strong( AzJX, Count, Temperature )
//lint -index( d, Count, Temperature )
// Only Count can index a Temperature
typedef float Temperature;
typedef int Count;
Temperature t[100]; // OK because of d flag
Temperature *pt = t; // pointers are also checked
// ... within a function
Count i;
t[0] = t[1]; // Warnings, no c flag
for( i = 0; i < 100; i++ )
t[i] = 0.0; // OK, i is a Count
119
pt[1] = 2.0; // Warning
i = pt - t; // OK, pt-t is a Count
上面的例子中,Temperature是被強索引類型,Count是強索引類型。如果沒有使用d 選項,數組的長度將被映射成固有的類型:
Temperature t[ (Count) 100 ];
但是,這是個小麻煩,像下面那樣將數組長度定義成常量更好一些:
#define MAX_T (Count) 100
Temperature t[MAX_T];
這樣做還有一個好處就是同樣的MAX_T還可以用在for 語句中,用於限制for語句的範圍。需要注意的是,指向強被索引類型的指針(例如上面的pt)如果用在[]符號(數組符號)中也會被檢查類型。其實,無論何時,只要將一個值加到一個指向強被索引類型的指針時,這個值就會被檢查以確認它是一個強索引類型。此外,強被索引指針如果減去一個值,其結果被認為是平常的強索引,所以下面的例子就不會產生告警:
i = pt - t;

變數跟蹤

變數值初始化跟蹤
早期的變數值跟蹤技術主要是對變數值的初始化進行跟蹤,和變數初始化相關的LINT訊息主要是644, 645 ("變數可能沒有初始化"), 771, 772 ("不可靠的初始化"), 530 ("未初始化的"), and 1401 - 1403 ("成員... 未初始化
")。以下面的代碼為例:
if( a ) b = 6;
else c = b; // 530 message
a = c; // 645 message
假設b 和c在之前都沒有初始化,PC-Lint就會報告b 沒有初始化(在給c賦值的時候)和c可能沒有被初始化(在給a賦值的時候)的訊息。而while 和for循環語句和上面的if語句稍微有所不同,比較下面的代碼:
while ( n-- )
{
b = 6;
...
}
c = b; //772 message
假設b 在使用之前沒有被初始化,這裡會報告b 可能沒有初始化的訊息(當給c賦值時)。之所以會有這樣的區別,是因為程式設計者可能知道這樣的循環體總是會被至少執行一次。相反,前面的if語句,對於程式設計者來說比較難以確定if語句是否總會被執行,因為如果是這樣的話,這樣的if語句就是多餘的,應該被去掉。While 語句和if比較相似,看下面的例子:
switch ( k )
{
case 1: b = 2; break;
case 2: b = 3;
/* Fall Through */
case 3: a = 4; break;
default: error();
}
c = b; //645 message
儘管b 在兩個不同的地方被賦值,但是仍然存在b 沒有被初始化的可能。因此,當b 賦值給c的時候,就會產生可能沒有初始化的訊息。為了解決這個問題,你可以在switch 語句之前給b 賦一個默認值。這樣PC-Lint 就不會產生告警訊息,但是我們也失去了讓PC-Lint檢查後續的代碼修改引起的變數初始化問題的機會。更好的方法是修改沒有給b 賦值的case語句。
如果error()語句代表那些“不可能發生”的事情發生了,那么我們可以讓PC-Lint 知道這一段其實是不可能執行的,下面的代碼表明了這一點:
switch ( k )
{
case 1: b = 2; break; case 2:
case 3: b = 3; a = 4; break;
default: error();
/*lint -unreachable */
}
c = b;
注意:這裡的-unreachable應該放在error()後面,break的前面。另外一個產生”沒有初始化”告警的方式是傳遞一個指針給free(或者採用相似的方法)。比如:
if( n ) free( p );
...
p->value = 3;
在訪問p 的時候會產生p 可能沒有被初始化的訊息。對於goto語句,前向的goto可能產生沒有初始化訊息,而向後的goto 會被忽略掉這種檢查。
if ( a ) goto label;
b = 0;
label: c = b;
當在一個大的項目中使用未初始化變數檢查時,可能會產生一些錯誤的報告。這種報告的產生,很大一部分來自於不好的程式設計風格,或者包括下面的結構:
if( x ) initialize y
...
if( x ) use y
當出現這種情況時,可以採用給y賦初始值的方式,或者利用選項-esym(644,y)關掉變數y上面的初始化檢查。
變數值跟蹤
變數值跟蹤技術從賦值語句、初始化和條件語句中收集信息,而函式的參數被默認為在正確的範圍內,只有在從函式中可以收集到的信息與此不符的情況下才產生告警。與變數值跟蹤相關的訊息有:
(1) 訪問地址越界訊息(訊息415,661,796)
(2) 被0除訊息(54,414,795)
(3) NULL 指針的錯誤使用(413,613,794)
(4) 非法指針的創建錯誤(416,662,797)
(5) 冗餘的布爾值測試(774)
看下面的例子:
int a[10];
int f()
{
int k;
k = 10;
return a[k]; // Warning 415
}
這個語句會產生警告415(通過'[' 訪問越界的指針),因為PC-Lint 保存了賦給k的值,然後在使用k的時候進行了判斷。如果我們把上面的例子稍加修改:
int a[10];
int f( int n )
{
int k;
if ( n ) k = 10;
else k = 0;
return a[k]; // Warning 661
}
這樣就會產生告警661 (可能訪問越界指針)。 使用“可能”是因為不是所有的路徑都會把10賦值給k。PC-Lint不僅收集賦值語句和初始化,還從條件語句中收集值的信息。比如下面的例子:
int a[10];
int f( int k, int n )
{
if ( k >= 10 ) a[0] = n;
return a[k]; // Warning 661 -- k could be 10
}
這裡仍然產生661告警,因為PC-Lint 檢測到,在使用k的時候,k的值>=10。另外,對於函式來說,它總是假設K 是正確的,程式使用者知道他們要做些什麼,所以下面的語句不會產生告警:
int a[10];
int f( int k, int n )
{ return a[k+n]; } // no warning
和檢查變數沒有初始化一樣,還可以檢查變數的值是否正確。比如,如果下面例子中的循環一次都沒有運行,k可能會超出範圍。這時候會產生訊息796 (可預見的地址訪問越界).
int a[10];
int f(int n, int k)
{
int m = 2;
if( k >= 10 ) m++; // Hmm -- So k could be 10, eh?
while( n-- )
{ m++; k = 0; }
return a[k]; // Info 796 - - k could still be 10
}
下面的例子演示了可能使用NULL 指針的問題:
int *f( int *p )
{
if ( p ) printf( "\n" ); // So -- p could be NULL
printf( "%d", *p ); // Warning
return p + 2; // Warning
}
這裡會產生兩個告警,因為可能使用了NULL 指針,很明顯,這兩個語句應該在if語句的範圍內。為了使你的程式更加健壯,你可能需要打開Pointer-parameter-may-be-NULL這個開關(+fpn)。這個選項假設所有傳遞到函式中的指針都有可能是NULL的。數組邊界值在高位被檢測,也就是說
int a[10]; ... a[10] = 0;
被檢測了,而a[-1]卻檢測不到。PC-Lint 中有兩個訊息是和指針的越界檢查有關的,一個是越界指針的創建,另外一個是越界指針的訪問,也就是通過越界指針獲取值。在ANSI C([1]3.3.6)中,允許創建指向超過數組末尾一個單元的指針,比如:
int a[10];
f( a + 10 ); // OK
f( a + 11 ); // error
但是上面創建的兩個指針,都是不能訪問的,比如:
int a[10], *p, *q;
p = a + 10; // OK
*p = 0; // Warning (access error)
p[-1] = 0; // No Warning
q = p + 1; // Warning (creation error)
q[0] = 0; // Warning (access error)
布爾條件檢查不象指針檢查那么嚴格,但是它會對恆真的布爾條件產生告警,比如:
if ( n > 0 ) n = 0;
else if ( n <= 0 ) n = -1; // Info 774
上面的代碼會產生告警(774),因為第二個條件檢查是恆真的,可以忽略。這種冗餘代碼不會導致問題,但它的產生通常是因為邏輯錯誤或一種錯誤可能發生的徵兆,需要詳細的檢查。
3.2.3 使用assert(斷言)進行補救
在某些情況下,雖然根據代碼我們可以知道確切的值,但是PC-Lint 卻無法獲取所有情況下變數的值的範圍,這時候會產生一些錯誤的告警信息,我們可以使用assert語句增加變數取值範圍信息的方法,來抑制這些錯誤的告警信息的產生。下面舉例來說明:
char buf[4];
char *p;
strcpy( buf, "a" );
p = buf + strlen( buf ); // p is 'possibly' (buf+3)
p++; // p is 'possibly' (buf+4)
*p = 'a'; // Warning 661 - possible out-of-bounds reference
PC-Lint 無法知道在所有情況下變數的值是多少。在上面的例子中,產生告警的語句其實並不會帶來什麼危害。我們可以直接使用
*p = 'a'; //lint !e661
來抑制告警。另外,我們還可以使用assert 工具來修正這個問題:
#include <assert.h>
... char buf[4];
char *p;
strcpy( buf, "a" );
p = buf + strlen( buf );
assert( p < buf + 3 ); // p is 'possibly' (buf+2)
p++; // p is 'possibly' (buf+3)
*p = 'a'; // no problem
由於assert在NDEBUG 被定義時是一個空操作,所以要保證Lint進行的時候這個宏沒有被定義。
為了使assert()和你的編譯器自帶的assert.h一起產生上面的效果,你需要在編譯選項檔案中添加一個選項。例如,假設assert 是通過以下的編譯器宏定義實現的:
#define assert(p) ((p) ? (void)0 : __A(...))
考慮到__A()會彈出一個訊息並且不會返回,所以這個需要添加的選項就是:
-function( exit, __A )
這個選項將exit函式的一些非返回特徵傳遞給__A 函式。做為選擇結果,編譯器可能將assert 實現成一個函式,例如:
#define assert(k) _Assert(k,...)
為了讓PC-lint 知道_Assert 是一個assert函式,你需要使用
-function( __assert, _Assert )選項或-function( __assert(1),
_Assert(1) )選項複製__assert()函式的語義
許多編譯器的編譯選項檔案中已經存在這些選項了,如果沒有的話,你可以複製一個assert.h 檔案到PC-lint 目錄下(這個目錄由於使用了-i 選項,檔案搜尋的順序優先於編譯器的頭檔案目錄)。
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),是作為告警信息的序言出現的。

順序檢查

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

弱定義

這裡的弱定義包含是以下內容:宏定義、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? ...

格式檢查

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

縮進檢查

根據代碼中的縮進問題,PC-Lint 也會產生相應的告警,因為縮進的問題有很大一部分是由於代碼結構不良或者大括弧的遺漏造成的。比如下面的例子:
if( ... )
if( ... )
statement
else statement
很明顯這裡的else是和第一個if語句對應的,而在這裡編譯器則把它和第二個if對應起來。PC-Lint會對這種情況產生告警。和這樣的縮進檢查相關的告警主要有三個725(no positive indentation)、525(negatively indented from)、539(Did 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中,會產生告警605(Increase in pointer capability),而把buf作為第二個參數傳
入strcpy函式中,會產生告警603(Symbol '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 */

使用方法

安裝配置

PC-lint 軟體性價比高,易於學習,容易推廣和固化到軟體開發測試流程中去,所以在全世界得到了廣泛的套用。PC-lint 使用方法很簡單,可以用命令行方式進行,例如lint-nt –u std.lnt test1.c test2.c test3.c 也可以使用
MAKEFILE的方式。此外,它還可以集成到很多開發環境或常用的代碼編輯軟體中,比如集成到Source Insight/SLICKEDIT/MS VC6.0/KEIL C..等。PC-Lint 還支持Scott Meyes的名著(Effective C++/More Effective C++)中說描述的各種提高效率和防止錯誤的方法。
PC-lint 的安裝非常簡單,以PC-lint 8.0為例,運行安裝程式將其釋放到指定的安裝目錄即可,比如c:\pclint8。然後需要運行PC-lint 的配置工具config.exe生成選項和檢查配置檔案,以剛才的安裝路徑為例,config.exe 應
該位於:C:\pclint8\config.exe。配置檔案是代碼檢查的依據,PC-lint 自帶了一個標準配置檔案std.lnt,但是這個檔案沒有目錄包含信息(頭檔案目錄),通常對代碼檢查的時候都需要指定一些特殊的包含目錄,所以要在標準配置的基礎上生成針對某個項目代碼檢查的定製配置。下面就以Microsoft Visual C++ 6的開發環境為例,介紹一下定製配置的過程。
運行C:\pclint8\config.exe後出現一個歡迎界面,提示版權資訊,如圖4.1所示:
PC-lint
圖4.1 配置歡迎視窗
點擊“下一步”按鈕出現pc-lint.exe命令行使用說明視窗(圖4.2所示):
圖4.2 pc-lint.exe命令行使用說明視窗
點擊“下一步”按鈕繼續,接著是選擇創建或修改已有配置檔案STD.LNT的選項:
圖4.3 選擇如何使用配置檔案STD.LNT 因為我們是第一次配置,所以選擇上面一個選項“Create a new TD.LNT”,
這樣做不會修改已有配置檔案STD.LNT的內容,而是創建一個新的STD_x.LNT檔案,檔案名稱中的x是從“a”到“z”26個英文字元中的任意一個,一般是按順序排列,從“a”開始。STD_x.LNT檔案的內容被初始化為STD.LNT內容的拷貝。如圖4.3所示,使用默認的PC-Lint 路徑,然後點擊“下一步”按鈕選擇編譯器:
圖4.4 選擇編譯器
接下來是選擇編譯器,在下拉框中選擇自己使用的編譯器。這裡我們選擇“Microsoft Visual C++ 6.x (co-msc60.lnt)”。如果沒有自己使用的編譯器,可選擇通用編譯器“Generic Compilers”。這個選項會體現在co-xxx.lnt 檔案中,並存放在前面我們選擇的配置路徑(C:\PCLint8)下,在後面配置選項我們所選擇的***.LNT均會被存放到這個路徑下。點擊“下一步”按鈕選擇記憶體模式:
圖4.5 選擇記憶體模式
可以根據自己程式區和數據區的實際大小選擇一個恰當的記憶體模型,記憶體模型的選項會體現在STD.LNT檔案或新創建的STD_x.LNT 中。因為我們的開發環境是32位的Windows,所以選擇“32-bit Flat Model”,然後點擊“下一步”按鈕選擇所要的支持庫的配置信息:
圖4.6 選擇軟體庫的配置信息
PC-Lint 常用的一些軟體庫都提供了定製的配置信息,選擇這些定製信息有助於開發人員將錯誤或信息的注意力集中在自己的代碼中,選擇的支持庫配置將被引入到STD.LNT檔案或新創建的STD_x.LNT 檔案中。選擇常用的ATL、MFC、STL等配置,然後點擊“下一步”按鈕:
圖4.7 選擇軟體名人的編程建議
這是一個比較有意思的選項,就是讓你選擇是否支持為使用C/C++編程提出過重要建議的作者的一些關於編程方面的個人意見。如果選擇某作者的建議,那么他提出的編程建議方面的選項將被打開,作者建議的配置名為AU-xxx.LNT,建議全部選擇,然後點擊“下一步”按鈕:
圖4.8 選擇是否設定包含檔案目錄
接下來是選擇用何種方式設定包含檔案目錄,如果選擇使用-i 方式協助設定包含檔案選項,下一步就會要求輸入一個或多個包含路徑。也可以跳過這一步,以後手工修改配置檔案,-i 選項體現在STD.LNT檔案或新創建的STD_x.LNT 檔案中,每個目錄前以-i 引導,目錄間以空格分隔,如果目錄名中有長檔案名稱或包含空格,使用時要加上雙引號,如-i“E:\Program Files\Microsoft Visual C++\VC98\Indlue”。這裡我們選擇用-i 方式協助我們來設定,然後點擊“下一步”按鈕:
圖4.9 選擇是否設定包含檔案目錄
這一步就是在下面的文本框裡可手工輸入檔案包含路徑,用分號“;”或用ctrl+Enter換行來分割多個包含路徑,或者可以點中Brows,在目錄樹中直接選擇。填完後點擊“下一步”按鈕:
圖4.10 提示std_x.lnt 已經被創建因為第三步選擇了“Create a new STD.LNT”選項,所以出現以下對話框,表
示std_x.lnt,std.lnt 在配置路徑下已被創建,這裡的std_a.lnt實際上包含了std.lnt 的信息,除此之外還有我們選擇的包含路徑和庫配置信息。單擊“確定”按鈕繼續:
圖4.11 提示是否為其它編譯環境創建配置檔案
選擇“確定”後,會接著提示是否為其它編譯環境創建配置檔案,如果選擇“是”將從第四步開始創建一個新的配置檔案。這裡我們選擇“否”:
圖4.12 是否替換std.lnt 檔案
接下來會提示是否使用現在生成的std_x.lnt 檔案取代std.lnt檔案。如果選擇“是”將會用std_x.lnt 檔案的內容覆蓋std.lnt 檔案的內容,使得當前創建的配置選項成為以後創建新的配置檔案時的預設配置。通常我們選擇“否”繼續下一步:
圖4.13 生成全局代碼檢查選項檔案OPTIONS.LNT
接下來將會準備產生一個控制全局編譯信息顯示情況的選項檔案OPTIONS.LNT,該檔案的產生方式有兩種,一種是安裝程式對幾個核心選項逐一解釋並提問你是否取消該選項,如果你選擇取消,則會體現在OPTIONS.LNT檔案中,具體體現方式是在該類信息編碼前加-e,後面有一系列逐一選擇核心選項的過程。如果選擇第二種選擇方式,安裝檔案會先生成一個空的OPTIONS.LNT檔案,等你以後在實際套用時加入必要的選項。這裡選擇“No”選項,即不取消這些選項,然後單擊“下一步”:
圖4.14 選擇所支持的集成開發環境
接著選擇所支持的集成開發環境選項,可選多個或一個也不選,PC-Lint 提供了集成在多種開發環境中工作的功能,例如可集成在VC、BC、Source Insight中。這裡我們選擇Microsift Visual C++ 6.0,這樣env-v6.lnt就會被拷貝到配置路徑中。然後單擊“下一步”:
圖4.15 選擇LIN.BAT 檔案的使用方式
安裝程式會生成一個LIN.BAT檔案,該檔案是運行PC-Lint 的批處理檔案,為了使該檔案能在任何路徑下運行,安裝程式提供了兩種方法供你選擇。第一種方法是讓你選擇把LIN.BAT拷貝到任何一個PATH 目錄下。第二種方法是生成一個LSET.BAT檔案,在每次使用PC-LINT前先運行它來設定路徑,或者把LSET.BAT 檔案的內容拷貝到AUTOEXEC.BAT檔案中。建議選擇第一種方法,指定的目錄為當前PC-Lint 的安裝目錄。我們選擇第一種方式:“copy LIN.BAT to one of my PATH directory”,然後單擊“下一步”輸入PATH 目錄:
圖4.16 指定PATH 目錄
輸入安裝目錄C:\PCLint8作為PATH目錄,然後單擊“下一步”按鈕進入最後的確認視窗:
圖4.17 確認完成配置
到此就完成了PC-Lint的安裝配置工作,單擊“完成”按鈕就可以使用PC-Lint。以上配置過程中在配置路徑下產生的多個*.lnt 檔案,除了std.lnt、std_x.lnt和option.lnt為配置嚮導所生成,其它co-xxx.lnt、lib-xxx.lnt、env-xxx.lnt 均是從原始安裝目錄中拷貝出來的,在這個目錄下還有其它PCLint所支持的編譯器、庫及集成開發環境的lnt 配置檔案,所有的lnt 檔案均為文本檔案。
上面的配置方法適合於剛開始接觸PC-lint 時使用,對於熟練的使用者可以直接編輯、編寫各*.lnt 配置檔案安成上面的配置工作,或者定製出更適合自己使用的配置環境。

開發工具

PC-Lint 的使用方法很簡單,可以用命令行方式進行,也可以集成到開發環
境中,下面就分別介紹這些用法
使用命令行方式
命令行的使用方式是PC-lint最基本的使用方式,也是其他各種集成使用方式的基礎,通過命令行可以完成PC-lint 的全部代碼分析工作。PC-lint 的命令行有下列形式:
Lint-nt option file1 [file1 file3 …]
其中的Lint-nt 是PC-lint 在Windows NT/2000/XP 平台上的可執行程式Lint-nt.exe,它完成PC-lint 的基本功能;option 代表PC-lint可接受的各種選項,這是PC-lint最為複雜的部分,它的選項有300多種,可以分為:錯誤信息禁止選項、變數類型大小選項、冗餘信息選項、標誌選項、輸出格式選項和其他選項等幾類,這些選項在本文的第三部分已經介紹過了;file為待檢查的源檔案。
另外值得注意的一點是,在命令行中可以加入前面提到的*.lnt配置檔案名稱,並可以把它看作是命令行的擴展,其中配置的各種選項和檔案列表,就和寫在命令行中具有一樣的效果。
PC-Lint 與Visual C++集成開發環境(IDE)集成
在所有集成開發環境中,PC-Lint 8.0對VC++6和VC++7.0的支持是最完善的,甚至支持直接從VC 的工程檔案(VC6是*.dsp,VC7是*.vcproj)導出對應工程的.Lnt檔案,此檔案包含了工程設定中的預編譯宏,頭檔案包含
路徑,源檔案名稱,無需人工編寫工程的.Lnt檔案。
PC-Lint 與VC 集成的方式就是在VC 的集成開發環境中添加幾個定製的命令,添加定製命令的方法是選擇“Tools”的“Customize...”命令,在彈出的Customize視窗中選擇“Tools”標籤,在定製工具命令的標籤頁中添加定製命令。首先要為VC 的集成開發環境添加一個導出當前工程的.Lnt配置檔案的功能,導出.Lnt檔案的命令行是:
lint-nt.exe +linebuf $(TargetName).dsp>$(TargetName).lnt 參數+linebuf表示加倍行緩衝的大小,最初是600 bytes,行緩衝用於存放當前行和你讀到的最長行的信息。$(TargetName)是VC集成開發環境的環境變數,表示當前激活的Project 名字,注意要選中“Use Output Window”選項,這樣PC-Lint 就會將信息輸出到Output視窗中。填寫效果如圖4.18所示:
圖4.18 添加導出項目.Lnt檔案的定製命令
接著添加一個檢查當前檔案的定製命令,檢查檔案的命令行為:
lint-nt.exe -i"C:\PCLint8" -u std_g.lnt env-vc6.lnt
"$(FileName)$(FileExt)"
第一個參數-i"C:\PCLint8"為PC-Lint 搜尋*.lnt 檔案的目錄,這裡就是我們的配置路徑。std_g.lnt是為VC 編譯環境定製的配置檔案,$(FileName)和$(FileExt)是VC 集成開發環境的環境變數,"$(FileName)$(FileExt)"表示當前檔案的檔案名稱。和導出.Lnt命令一樣,這個命令也要使用VC 集成環境的Output視窗輸出檢查信息,所以要選中“Use Output Window”選項,如圖4.19所示:
圖4.19 添加檢查當前檔案檔案的定製命令
最後要添加一個檢查整個工程的定製命令,檢查整個工程的命令行是:
lint-nt.exe +ffn -i"C:\PCLint8" std_g.lnt env-vc6.lnt
$(TargetName).lnt>$(TargetName).chk
這個命令的結果就是將整個工程的檢查結果輸出到與工程同名的.chk檔案中。參數中+ffn 表示Full File Names,可被用於控制是否使用的完整路徑名稱表示。
下面就以一個簡單的例子介紹一下如何在VC集成開發環境中使用PC-Lint。首先新建一個“Win32 Console Application”類型的簡單工程(輸出“Hello World”),然後將本文第二章引用的例子代碼添加到工程的代碼中,最後將這個工程代碼所倚賴的包含目錄手工添加到配置檔案中,因為代碼檢查要搜尋stdafx.h這個預編譯檔案,所以本例要手工添加工程代碼所在的目錄。本文的例子生成的配置檔案是std_g.lnt,用文本檔案打開std_g.lnt,在檔案中添加一行:
-iC:\unzipped\test
“C:\unzipped\test”就是例子工程所在的目錄(stdafx.h就在這個目錄)。如果你的工程比較龐大,有很多頭檔案包含目錄,就需要將這些目錄一一添加到配置檔案。在確保代碼輸入沒有錯誤之後(有錯誤頁沒關係,PC-Lint會檢查出錯誤),就可以開始代碼檢查了。例子工程,打開要檢查的代碼檔案,本例是test.cpp,然後選擇“Tools”選單下的“PC_LINT 8.0 Check Current File”命令,Output 視窗輸出對本檔案的檢查結果,如圖4.20所示:
圖4.20 檢查結果
PC-Lint 與source insight 集成
PC-Lint 與source insight的集成也是通過添加定製命令實現的,從“Options”選單中選擇“Custom Commands”命令項。點擊“Add…”按鈕,如圖4.21所示,在彈出的“Custom Commands”視窗中完成以下輸入:
在Name欄中輸入“PC-lint Check Current File”,原則上這個名稱可以隨便起,只要你能搞清楚它的含義就可以了;
在Run 欄中輸入“C:\PcLint\lint-nt -u -iC:\PcLint\Lint std_f env-si %f”其中C:\PcLint 是你PC-LINT的安裝目錄,td_f表示為Source Insight定製的配置檔案std_f.lnt;
在Output欄中選擇“Iconic Window”、“Capture Output”選項;
在Control 欄中選擇“Save Files First”;
在Source Links in Output 欄中選擇“Parse Links in Output”、“File,then Line”;
在Pattern 欄中輸入“^\([^ ]*\) \([0-9]+\)”;
圖4.21 在Source Insight中添加定製命令
命令添加完成後就可以點擊“Run”按鈕就可以對當前檔案執行PC-Lint 檢查。為了方便使用,還可以點擊“Menu...”按鈕將這個定製命令添加到Source Insight的選單中。
PC-Lint 與UltraEdit 集成
在UltraEdit 中集成PC-Lint的方法和Source Insight 類似,也是添加一個定製命令選單,具體實現方法是先單擊UltraEdit 的“高級”選單中的“工具配置”命令,如圖4.22所示,在打開的配置視窗中依次輸入以下內容:
在“選單項目名”欄輸入“PC-lint Check Current File”;
在“命令行”欄輸入以下命令:C:\PCLint\lint-nt –u -iC:\PCLint std env-si %f 其中,C:\PCLint是PC-Lint的安裝目錄,使用std.lnt 中的配置,由於UltraEdit 和Source Insightde 的檢查環境類似,所以借用env-si 中的環境配置;
在“工作目錄”欄輸入以下路徑:E:\code,這是代碼所在目錄;
選中“先保存所有檔案”選項;
在“命令輸出”欄中,選中“輸出到列表”和“捕捉輸出”兩個選項;
點“插入”將命令行插入UltraEdit 的選單中;
圖4.22 在UltraEdit中添加定製命令此時在UltraEdit 的“高級”選單中會增加一個“PC-lint Check Current File”選單,點擊該選單即可對當前檔案執行PC-lint檢查。

特殊情況

由於各子系統代碼風格或個人編程風格的差異,可能存在統一選項檔案中的某個選項在大部分代碼中是非常嚴重,必須排查的,而在某類代碼中是輕微的,或者雖然也嚴重但對它的修改將會涉及眾多檔案的修改,這種眾多檔案的修改在短期內無法完成,對於這類特殊情況將可以通過在代碼中加pclint編譯信息,禁止相關選項的檢查。但對於這類特殊情況,需要制定流程,要求提交相關的說明。

總結

軟體除錯是軟體項目開發成本和延誤的主要因素,PC-lint 能夠幫你在程式動態測試之前發現編碼錯誤,降低軟體消除錯誤的成本。使用PC-Lint 在代碼走讀和單元測試之前進行檢查,可以提前發現程式隱藏錯誤,提高代碼質量,節省測試時間。另外,使用PC-lint的編碼規則檢查,可以有效地規範軟體人員的編碼行為。如果能夠在軟體開發過程中有效地使用PC-lint 代碼檢查工具,將大大地提高代碼質量,降低軟體成本。
參考文獻
[1] Gimpel Software. Reference Manual for PC-lint/FlexeLint. July,2001
[2] PC-Lint選項詳解

附錄一

Msg.txt :解釋告警的內容。
options.lnt :反映全局編譯信息顯示情況的選項檔案,通常需要添加自定選項以使代碼檢查更為嚴格。 env-xx.lnt :講述如何將PC-lint 與對應的編輯環境結合起來,xx是si表示是為Source Insight配置的檢查環境,xx是vc6則表示是為Visual C++ 6.0準備的檢查環境。
co-xxx.lnt :選定的編譯器與庫選項。
std.lnt :標準配置檔案,包含記憶體模型等全局性東西。
lib-xxx.lnt :庫類型的列表,包括標準C/C++庫,MFC庫,OWL庫等等。
au-xxx.LNT :C++編程提出過重要建議的作者,選擇某作者後,他提出的編程建議方面的選項將被打開。

附錄二

命令格式
說明
代碼中的舉例
-e#
隱藏某類錯誤
/*lint -e725 */
-e(#)
隱藏下一表達式中的某類錯誤
/*lint –e(534) */
printf(“it’s all”);
!e#
隱藏本行中的錯誤
/*lint !e534*/
printf(“it’s all”);
-esym(#, Symbol)
隱藏有關某符號的錯誤
/*lint –esym(534, printf)*/
printf(“it’s all”);
-elib(#)
隱藏頭檔案中的某類錯誤
/*lint –elib(129) */
#include “r01.h”
-efunc(#, <func>)
隱藏某個函式中的特定錯誤
/*lint –efunc(534, mchRelAll)*/
unsigned int mchRelAll
(mchHoData*pHoData)
{ printf(“it’s all”); }

附錄三

錯誤編碼
錯誤說明
舉例
40
變數未聲明
506
固定的Boolean值
charc=3;if(c<300){}
525
縮排格式錯誤
527
無法執行到的語句
if(a>B) return TRUE;
else return FALSE;
return FALSE;
529
變數未引用,檢查變數未引用的原因
530
使用未初始化的變數
534
忽略函式返回值
539
縮排格式錯誤
545
對數組變數使用&
chararr[100],*p;
p=&arr;
603
指針未初始化
void print_str(constchar*p);…
char*sz;
print_str(sz);
605
指針能力增強
void write_str(char*lpsz);…
write_str(“string”);
613
可能使用了空指針
616
在switch語句中未使用break;
650
比較數值時,常量的範圍超過了if(ch==0xFF)...變數範圍
713
把有符號型數值賦給了無符號型數值
715
變數未引用
725
Indentation錯誤
734
在賦值時發生變數越界
int a,b,c;…
c=a*b;
737
無符號型變/常量和有變數型變/常量存在於同一個表達式中。
744
在switch語句中沒有default
752
本地聲明的函式未被使用
762
函式重複聲明
774
Boolean表達式始終返回真/假
char c;
if(c<300)

相關詞條

熱門詞條

聯絡我們