pc_lint的用法轉


PC-Lint是一款C/C++軟件代碼靜態分析工具,不僅可以檢查一般的語法錯誤,還可以檢查潛在的錯誤,比如數組訪問越界、內存泄漏、使用未初始化變量、使用空指針等。在單元測試前使用PC-Lint來檢查代碼,可以提前發現程序中的潛在的錯誤,提高代碼
的質量。
本文主要從兩個方面介紹PC-lint,第一部分是在與不同的工具安裝集成,第二部分是PC-lint的使用方法。

1. 安裝PC-lint及如何集成到VC6.0和Source Insight
1.1 安裝PC-lint
(1)下載PC-lint(PC-Lint 8.00w)。
(2)將下載的壓縮包解壓至到D盤,並對文件夾重命名為PC-Lint,這樣路徑為D:\PC-Lint。
備注:這也叫安?呵呵,似乎算免安裝更合適吧。
1.2 將PC-lint集成到VC6.0
1.2.1 對單個C/C++進行靜態代碼分析
(1)將D:\PC-Lint\lnt 下的3個文件lib-w32.lnt,env-vc6.lnt,co-msc60.lnt復制到D:\PC-Lint\下。
(2)打開co-msc60.lnt,將該文件倒數第二行"lib-ole.lnt"的內容改為"D:\PC-Lint\lnt\lib-ole.lnt",也就是在前面加上絕對路徑,以免在后面的步驟中無法找到該文件。
(3)在D:\PC-Lint\下創建std.lnt和options.lnt兩個文件,其中std.lnt的內容如下:

注:-i后面的路徑名為VC 6.0的安裝路徑和及其頭文件路徑;options.lnt可以暫時為空。
(4)在VC6.0的菜單欄中,Tools--->Customize...-->tools 新建一個名為pclint的項,在下面填入
"Command"項填入: D:\PC-Lint\lint-nt.exe
"Argument"項填入: -u D:\PC-Lint\std.lnt  D:\PC-Lint\env-vc6.lnt "$(FilePath)"

然后在Use Output Window 打上勾即可。
(5)在VC6.0的菜單欄Tools下多了一個pclint選項,打開一個VC項目后,就可以使用該選項對單個C/C++文件進行靜態代碼分析了。

1.2.2 對一個VC6.0項目進行靜態代碼分析
下面的步驟是在1.2.1的前三步的基礎上進行的。
(1)先到http://www.weihenstephan.de/~syring/win32/UnxUtils.zip下載UnxUtils.zip。需要利用unix中的find等命令來查找當前目錄下的C和C++文件,然后再將它們送給lint程序處理。
(2)解壓UnxUtils.zip到D盤,這樣路徑為D:\UnxUtils。
(3)在在VC6.0的菜單欄Tools下多了一個pclint_prj選項,打開一個VC項目后,就可以使用該選項對單個C/C++文件進行靜態代碼分析了。
"Command"項填入: D:\UnxUtils\usr\local\wbin\find.exe
"Argument"項填入: $(FileDir) -name *.c -o -name *.cpp | D:\UnxUtils\usr\local\wbin\xargs D:\PC-Lint\lint-nt -i"D:\UnxUtils\usr\local" -u D:\PC-Lint\std.lnt D:\PC-Lint\env-vc6.lnt

然后在Use Output Window 打上勾即可。
(4)在VC6.0的菜單欄Tools下多了一個pclint_prj選項,打開一個VC項目后,就可以使用該選項對VC項目進行靜態代碼分析了。
注意:"Argument"項填的內容一定要注意參數中的路徑,如果你不使用上述路徑,可以用新路徑將參數中的路徑替換,以免重新寫參數而導致出錯。

1.3 將PC-lint集成到Source Insight 3.5中
1.3.1 對單個C/C++進行靜態代碼分析
(1)打開SourceInsight, 選擇Options-->Custom Commands-->Add, 輸入pclint
(2)在Run中填寫: D:\PC-Lint\lint-nt -u D:\PC-Lint\std.lnt D:\PC-Lint\env-vc6.lnt %f
(3)Dir不用填寫,將Iconic Window, Capture Output, Parse Links in OutPut,三項勾選上,並將File,then Line的單項選擇也選上。
(4)然后點右側的Menu...,在彈出的界面中在下拉框Menu中選擇View,然后在下面的Menu Cotents中選擇<end of menu>, 右側點Insert即可。
(5)可以在Source Insight 3.5菜單View下看到剛才新建的項pclint,打開項目的任意一個待分析的源文件,運行pclint即可進行靜態代碼分析了。

1.3.2 對一個項目進行靜態代碼分析
下面的步驟是在1.2.2的基礎上進行的。
(1)打開SourceInsight, 選擇Options-->Custom Commands-->Add, 輸入pclint_prj
(2)在Run中填寫:
D:\UnxUtils\usr\local\wbin\find.exe %d -name *.c -o -name *.cpp | D:\UnxUtils\usr\local\wbin\xargs D:\PC-Lint\lint-nt -i"D:\UnxUtils\usr\local" -u D:\PC-Lint\std.lnt D:\PC-Lint\env-vc6.lnt
(3)Dir不用填寫,將Iconic Window, Capture Output, Parse Links in OutPut,三項勾選上,並將File,then Line的單項選擇也選上。
(4)然后點右側的Menu...,在彈出的界面中在下拉框Menu中選擇View,然后在下面的Menu Cotents中選擇<end of menu>, 右側點Insert即可。
(5)可以在Source Insight 3.5菜單View下看到剛才新建的項pclint_prj,打開項目,運行pclint_prj即可對項目進行靜態代碼分析了。

2.PC-lint的用法

2.1 pc-lint目錄下幾個重要的文件及程序
lint-nt.exe:PC-lint的可執行程序。
config.exe: PC-lint的配置文件程序。
pc-lint.pdf:PC-lint的PDF格式的在線手冊,本文的大部分內容是從中得來的。
msg.txt:     對於錯誤消息編號的詳細解釋。
Lnt\:       這個目錄下有些東西還是值得認識一下。
co-....lnt: 指定的編譯器的可選編譯文件。
co.lnt:     通用的可選編譯文件。
sl-....c     非ANSI編譯器的標准庫文件模塊
sl.c:        非ANSI編譯器的通用標准庫文件模塊
env-....lnt:不同平台下的可選文件,包括MS Visual Studio和其他各種編輯工具。
lib-....lnt:可選文件, 特定的"有挑戰性"的庫文件。
au-....lnt: 可選文件, 作者們推薦的檢測條件。

2.2 PC-lint的介紹
2.2.1 錯誤信息編號
對於大部分的錯誤消息,PC-lint都提供了一個關聯的錯誤編號。小於1000的錯誤編號是分配給C語言的,1000以上的錯誤編號則是分配給C++語言的。1000呢?呵呵,被保留了。先看一個表格。
                                 C              C++           告警級別
語法錯誤(Syntax Errors)      1   - 199      1001 - 1199           1
內部錯誤(Internal Errors)    200 - 299                            0
致命錯誤(Fatal Errors)       300 - 399                            0
告警(Warnings)               400 - 699      1400 - 1699           2
提示(Informational)          700 - 899      1700 - 1899           3
可選信息(Elective Notes)     900 - 999      1900 - 1999           4

對於C語言,1~199是與語法錯誤;200~299是PC-lint內部錯誤,應該決不會發生的;300~399是致命錯誤,通常是由於超越了某些限制;400~699是警告消息,提示被檢查的程序中可能存在錯誤;700~899是提示信息,這些提示信息可能有錯誤,也可能是合法的程序,取決於個人的編程風格;900~999則是一些稱為可選信息,一般不會自動輸出。

PC-lint提供了高級級別設置選項-wLevel,缺省的級別為3級。-w0, -w1 , -w2, -w3, -w4 分別可以生成上述表格中對應告警級別和級別更低的告警,其中級別越低告警越重要。同樣,也提供了處理庫函數的頭文件告警級別的選項-wlib(Level),缺省的級別也是3級,級別對應的含義與前者一樣。

2.2.2 選項的規則
通過使用加號"+"和減號"-",以注釋的形式插入代碼中,來恢復和屏蔽指定的被檢查的選項。格式如下:
/*lint option1 option2 ... optional commentary */
或者
//lint option1 option2 ... optional commentary
注意:lint必須是小寫,選項的一行不能超過80個字符,否則導致致命的錯誤,錯誤信息的編號就是323。如果選項確實有很長,可以通過換行的方式來實現。另外屏蔽和恢復的選項的代碼可以放在宏定義中,宏被展開后,這些選項會生效。

2.2.3 選項中的空格
因為空格是用來分隔選項的,除此之外只能出現在圓括號的旁邊或是空格自身被引用(例如operator new按語法要求中間就有空格)。舉個例子:
-esym(534,printf,scanf,operator new)
-esym(534, printf, scanf, operator new)
-esym( 534 , printf , scanf , operator new )
對於第三個,空格出現在圓括號的旁邊,也出現在自身被引用的地方(operator new)。另外operator和new之間出現兩個空格也是不合法的,因為它違反了語法規則。另外,也可以使用雙引號(""來保護空格,例如:
-"dWORD=unsigned short"

2.2.4 選項的分類
PC-lint的選項有300多種,可以分為下面幾類:
(1)禁止錯誤信息
選項開頭使用"-e"可以禁止指定的錯誤消息,使用"+e"恢復指定的錯誤消息。如果禁止消息,只不過不讓消息輸出,並不影響PC-lint的處理過程。順便提一下前面提到的"-wLevl",這個選項是禁用指定級別及以上的消息的顯示。

1)格式一:
-e#  禁止指定的錯誤消息,#代表數字或是數字匹配符,錯誤消息的編號為#。
+e#  恢復指定的錯誤消息,錯誤消息的編號為#。

舉個例子:
/*lint -504*/
...Code.....
/*lint +504*/
第一行關閉了編號為504的錯誤消息,最后一個行則重新打開了編號為504的錯誤消息。其中數字也可以包含匹配符號,'?'匹配單個字符,"*"匹配多個字符。
比如:
(1)-e7???, 則關閉了700~799這個范圍內的錯誤消息。
(2)-e1*, 則關閉了所有以1開頭的編號的錯誤消息。
同樣匹配符也能使用在-esym, -elib, -elibsym, -efile, -efunc, -emacro, -etemplate, -e(#), --e(#), -e{#} and –e{#}.

2)格式二:
-e(#[,#]...) 為下一個表達式禁止指定的錯誤消息,在這個表達式結束后被禁止的錯誤消息自動恢復,#代表數字或是數字匹配符,錯誤消息的編號為#。
舉個例子:
a = /*lint -e(413) */ *(char *)0;
它等價於下面的語句:
a = /*lint -save -e413 */ *(char *)0
/*lint -restore */;
前一種方法更簡單且更有效。

3)格式三:
--e( # [,#]... ) 比上面的那個管的更寬一些,它對整個表達式有效,舉個例子就明白它與上面的區別了。
舉個例子:
a = /*lint --e(413) */ *(int *)0 + *(char *)0;
整個表示式是指*(int *)0 + *(char *)0,下個一表達式指的是*(int *)0。區別一目了然,例子中將禁止兩個編號為413 的錯誤消息, 如果使用 -e(413) ,則只禁止第一個編號為 413 的錯誤消息。

4)格式四:
-e{ # [, #] …} 對下一個語句或者聲明有效
舉個例子:
//lint -e{715} suppress "k not referenced"
void f( int n, unsigned u, int k )
{
    //lint -e{732} suppress "loss of sign"
    u = n; // 732 not issued
    //lint -e{713} suppress "loss of precision"
    if(n)
   {
       n = u; // 713 not issued
   }
} // 715 not issued
通過例子可以看出,這種格式放在函數之前,則對整個函數產生作用,放在賦值語句前則只對賦值語句起作用,放在if或while前面,則對這一段語句起作用。在C++的類定義或命名空間聲明前放這么個選項,則將整個類或命名空間內的代碼中指定的錯誤消息給禁止了。

5)格式五:
--e{ # [, #] … } 對於其所處的 {} 號區域內的整個代碼體有效。 {} 號區域可能是復雜的語句、函數體、C++的類,結構體或聯合體的定義、C++的命名空間等。如果這個選項放在一個模塊前,而模塊前沒有 {},則對整個模塊生效。

6)格式六:
!e# 僅對其所在行有效。
if( x = f(34) ) //lint !e720
y = y / x;
在這個例子中,僅對那一行禁止編號為720 的錯誤消息。看一下C語言的代碼的用法:
if( x = f(34) ) /*lint !e720 */
y = y / x;
如果有更多的錯誤信息要禁止,而又無法使用通配符,則可以使用下面的方法:
n = u / -1; //lint !e573 !e721

7)格式七:
-ealetter 參數不匹配禁止

格式八:
-efile( #, file [, file] ... ) inhibits and
+efile( #, file [, file] ... ) re-enables

9)格式九:
-efunc( #, Symbol [, Symbol] ... ) inhibits and
+efunc( #, Symbol [, Symbol] ... ) re-enables

10)格式十:
-elib( # [, #] ... ) inhibits and
+elib( # [, #] ... ) re-enables

11)格式十一:
-elibsym( # [, # ] ... ) inhibits
+elibsym( # [, # ] ... ) re-enables

12)格式十二:
-emacro( #, symbol, ... )
+emacro( #, symbol, ... )

13)格式十三:
-emacro( (#), symbol, ... )
--emacro( (#), symbol, ... )
-emacro( {#}, symbol, … )
--emacro( {#}, symbol, … )

14)格式十四:
-esym( #, Symbol [, Symbol] ... ) inhibits and
+esym( #, Symbol [, Symbol] ... ) re-enables
禁止和恢復指定的符號的錯誤消息。舉個C++的例子(實際應用中不太可能出現):
class X
{
    void f(double, int);
};
分析結果中會提示某行的member X::f(double, int)沒有被引用,為了屏蔽這個消息,你可以使用
-esym( 754, X::f )
符號的完整簽名為X::f(double, int),然而符號的名字為X::f,而且可以使用符號的名字來禁止錯誤消息的出現。另外,-esym 和 -e# 選項是獨立的,舉個例子:
-e714 +esym( 714,alpha )
對於alpha來說,它禁止了編號為714的錯誤消息,第二個選項並不會恢復編號為714的錯誤消息,除非前面有個對應的-esym(714,alpha)。

15)格式十五:
-etd( TypeDiff [, ...] ) inhibits
+etd( TypeDiff [, ...] ) re-enables

16)格式十六:
-etemplate( # [,#] ... )
+etemplate( # [,#] ... )
禁止和恢復在擴展模板(expanding templates)時的錯誤消息。

(2)變量類型大小和對齊選項
1)變量類型大小選項
這組選項允許設置各種變量類型的大小和對齊方式。由於默認的設置於絕大多數的編譯器都是一致的,所以這些參數的單獨設置通常是沒有必要的。使用變量類型大小的選項是為了特定的架構,而不是本地架構。舉個例子,你需要為嵌入式系統設置int和pointers通常為16位,那么你應該指定:
lint -si2 -sp2 ...
下面的列表,#號代表一個小的整型值,僅舉幾個:
-sb# 字節的位數為#,默認的是-sb8,
-sbo# sizeof(bool)就變為一個參數了,默認值為1,
-sc# sizeof(char) 就變為 #,默認值為1,
-slc# sizeof(long char) 就變為 #,默認值為2,
...
2)對齊選項
僅有兩個可選擇的注釋信息來檢測不規律的對齊,它們的錯誤編號是958和959,詳細的介紹就省略了吧。

(3)冗長信息選項
冗長信息選項采用-v和+v開頭來控制,冗長信息指的是在檢測過程中產生的一些與編譯過程有關的信息,或者說,冗長信息與編譯過程中消息的頻率和種類有關。如果使用-v,則冗長信息進被發送到標准輸出,而是用+v,冗長信息進則會被發送到標准輸出和標准錯誤中。如果要將錯誤信息重定向到一個文件中,並想要在終端查看冗長信息和被解釋后的錯誤消息,+v選項是非常有用的。

(4) 標志選項
采用+f,++f,-f,--f開頭開介紹標志位。一個標志位在內部采用一個整型值表達。通過認為:
ON  假如整型值大於0
OFF 假如整型值小於或等於0
默認設置時1為ON,0為off,對於的關系如下:
+f...:通過把標志為設置為1而把它設置為ON
-f...:通過把標志為設置為0而把它設置為OFF
++f...:標志位增1
--f...:標志位減1
后面兩個選項在只設置局部標志而不影響全局設置時,非常有用。

(5)消息顯示選項
消息顯示選項用於定義消息輸出的格式。
1)控制錯誤消息的高度。
-h選項被用來控制消息的高度,通常的格式如下:
-h[F][f][a][r][mn][m][m/M/][I]N
s 表示每條消息后的空格。其他的就不介紹了。
2)控制錯誤消息的寬度。
格式如下:
-width(W,Indent)
例如:-width(99,4)
3)消息格式化選項
格式如下
-format=...
3)附加信息選項
格式如下:
-append(errno,string)
(6)其他選項
1)-A 要求嚴格使用ANSI C/C++處理。
其他的不介紹了。

2.2.5 庫文件檢查
這里的庫文件時指那些編譯后的庫文件,比如標准的I/O庫,又比如第三方的庫文件,例如windows的庫文件。關注庫文件的重要特色是因為以下兩點:
(1)庫文件的源代碼通常不可獲得。
(2)庫文件在多個正被你使用pc-lint檢查的程序中使用。
庫的頭文件描述了庫的部分或完整的接口。舉個例子:
hello.c
#include <stdio.h>
main()
{
    HelloWorld();
    printf( "hello world\n" );
}

如果沒有"#include <stdio.h>"這一行代碼,使用PC-lint檢查上述代碼,PC-lint會抱怨printf()既沒有聲明也沒有定義,會給出編號為718錯誤信息。如果"stdio.h"被當做一個庫文件的頭文件,那么PC-lint不會要求給出printf()的源代碼。

(1)格式一:
+libclass( identifier[, identifier] ... )
用來指定名為identifier的頭文件當做庫頭文件。identifier是其中下面之一:
angle: 所有尖括號包含起來的頭文件
foreign:所有在搜索列表中目錄下的頭文件
ansi:標准ANSI C/C++ 的頭文件
all:所有頭文件
默認情況下,+libclass(angle,foreign) 是有效的,這也是為什么hello.c的代碼沒有要求給出printf()源代碼的原因。
(2)格式二:
+libdir( directory [, directory] ... )
-libdir( directory [, directory] ... )
指定目錄的。
(3)格式三:
+libh( file [, file] ... )
-libh( file [, file] ... )
增加或移出那些已經被 +libclass 和 +/-libdir 已確定的頭文件,以達到要求或不要求給出源代碼。舉個例子:
+libclass( ansi, angle )
+libh( windows.h, graphics.h )
+libh( os.h ) -libh( float.h )
要求所有的ansi和angle(除了float.h),還有那三個windows.h, graphics.h, os.h也會被當做庫頭文件。

2.2.6 強類型檢查
什么是強類型?C/C++的變量都有類型,不同類型之間的賦值可能會產生告警,可以說C/C++變量的類型是強類型。有強類型,自然有弱類型。比如一些腳本語言,它們的變量就不存在具體的類型,可以相互之間賦值,它們就是弱類型語言。為什么在使用PC-lint對C/C++進行檢查時,要進行強類型檢查呢?因為有諸如使用typedef定義的數據類型,可以避開編譯器的類型檢查。舉個例子:
typedef int Count;
typedef int Bool;
Count n;
Bool stop;
...
n = stop ;

對於這段代碼,編譯器是不會告警的,但是最后一行代碼是錯誤的。所以,強類型檢查選項是必要的。
   強類型檢查選項"-strong"和附加選項"-index"可以完全的或部分的對typedef定義的數據類型進行強類型檢查,保證相同類型的變量才能相互賦值。
(1)強類型檢查選項strong的格式如下:
-strong( flags[, name] ... )
name是強類型,flags是指定的屬性,flags參數可以是A、J、X、B、b、l和f。如果name被省略,所有使用typedef定義的數據類型的flags的屬性不能被其他的-strong選項所識別。

flags參數  弱化字符
   A       i 忽略初始化
           r 忽略Return語句
           p 忽略參數傳遞
           a 忽略賦值操作
           c 忽略將常量賦值(包括整數常量、常量字符串等)給強類型的情況
           z 忽略Zero賦值

   X       當把強類型的變量賦值給其他變量的時候進行類型檢查。弱化參數i, r, p, a, c, z同樣適用於X並起相同的作用。
   J       當強類型與其它類型進行運算時(邏輯運算、關系運算、數學運算等)進行檢查
           e 忽略==、!=和?:操作符
           r 忽略>、>=、<和<=
           o 忽略+、-、*、/、%、|、&和^
           c 忽略該強類型與常量進行以上操作時的檢查
           z 忽略該強類型與Zero進行以上操作時的檢查
   B       類型是Boolean,一般情況下只能使用一個name(指格式中的name), 而且它應該和其他flags聯合使用。
           B選項有兩個作用:
           1. 出於強類型檢查的目的,每一個Boolean操作符都采用返回一個和Type兼容的類型。Boolean操作符就是那些顯示
              為true或false,也包括前面提到的四種關系運算符和兩種等於判斷符,取反操作符!,二元操作符&&和||。
           2. 在所有需要判斷Bolean值的上下文中,比如if語句和while語句,都應該檢查這個強類型,否則產生告警。
   b      僅僅假定每一個Bolean類操作符都將返回一個與Type類型兼容的返回值。與B選項相比,b選項的限制比較寬松。
   l      庫標志,當強類型的對象從庫函數中獲得值,或者將強類型對象的值作為參數傳遞給庫函數等情況下,不產生告警。
   f      與B或b連用,表示不應該將1位長度的位域當做Boolean類型,否則表示1位長度的位域被缺省假定為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),這個告警就會被禁止。
(2) 另一個強類型檢查選項是index,格式如下:
-index( flags, ixtype, sitype [, sitype] ... )
這個選項是對strong選項的補充,它可以和-strong選項一起使用。這個選項指定ixtype是一個排他的索引類型,它可以和強索引類型sitype的數組(或指針)一起使用,ixtype和sitype被假定為后來使用typedef聲明來定義的的類型名稱。flags可以是c或d,c允許將ixtype和常量作為索引使用,而d允許在不使用ixtype的情況下指定數組的維數(Dimensions)。

 

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常用的,也是比較重要的代碼檢查類型,並舉例介紹了各個檢查類型下可能出現的告警信息以及常用選項的用法:

3.1 強類型檢查

    強類型檢查選項“-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;

3.2 變量值跟蹤

3.2.1 變量值初始化跟蹤

    早期的變量值跟蹤技術主要是對變量值的初始化進行跟蹤,和變量初始化相關的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上面的初始化檢查。

3.2.2 變量值跟蹤

    變量值跟蹤技術從賦值語句、初始化和條件語句中收集信息,而函數的參數被默認為在正確的范圍內,只有在從函數中可以收集到的信息與此不符的情況下才產生告警。與變量值跟蹤相關的消息有:
(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),是作為告警信息的序言出現的。

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會檢查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也可以被用來描述非標准的 % 碼。

3.6 縮進檢查

    根據代碼中的縮進問題,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個空格長度。另外,縮進檢查還和代碼的編碼格式策略相關,需要進行必要的調整。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM