程序員是追求完美的一族,即使是一般的程序員大多也都不想看到自己的程序中有甚至那么一點(diǎn)點(diǎn)的瑕疵。遇到任意一條編譯器警告都堅(jiān)決不放過。有人會(huì)說:我們可以使用比編譯器更加嚴(yán)格的靜態(tài)代碼檢查工具,如splint。這個(gè)建議也很不錯(cuò)。不過lint工具使用起來較繁瑣,有時(shí)候還需要記住一些特定符號(hào)并插入到你自己的代碼中才行,門檻較高,這也讓很多人止步于此。那么我們就從此放棄么?不,如今的編譯器做得都很好,它可以幫助我們的找到絕大多數(shù)可能出現(xiàn)問題的代碼,前提是你要學(xué)會(huì)控制編譯器去找到這些問題代碼,而熟悉編譯器的警告選項(xiàng)恰恰是體現(xiàn)控制力的好方法。當(dāng)你可以自如控制編譯器警告輸出的時(shí)候,你就算是'入道'了,同時(shí)你對(duì)語(yǔ)言的理解也更進(jìn)一步了。
有人說:我就是用一個(gè)-Wall選項(xiàng)就可以了,一般選手可以這么做,而且他可以不知道-Wall會(huì)跟蹤哪些類型的問題;但是高級(jí)選手是不會(huì)只使用-Wall的,他會(huì)把每條警告都研究的很透徹,會(huì)在Makefile中列出他想讓編譯器輸出哪些類型的警告以替代-Wall,他會(huì)屏蔽掉那些對(duì)他的代碼'毫無(wú)用處'的警告(很可能他使用了編譯器對(duì)語(yǔ)言的擴(kuò)展功能),他會(huì)有個(gè)和編譯器交流的過程。
俗話說:'工欲善其事,必先利其器',一直在工作中使用GNU C編譯器(以下簡(jiǎn)稱GCC),這里對(duì)GCC的一些警告選項(xiàng)細(xì)致的分析,并列舉幾個(gè)簡(jiǎn)單的例子[注1]供分析參考。
1. -Wall集合警告選項(xiàng)
我們平時(shí)可能大多數(shù)情況只使用-Wall編譯警告選項(xiàng),實(shí)際上-Wall選項(xiàng)是一系列警告編譯選項(xiàng)的集合。下面逐一分析這一集合中的各個(gè)選項(xiàng):
[-Wchar-subscripts]
如果數(shù)組使用char類型變量做為下標(biāo)值的話,則發(fā)出警告。因?yàn)樵谀承┢脚_(tái)上char可能默認(rèn)為signed char,一旦溢出,就可能導(dǎo)致某些意外的結(jié)果。
e.g.
/* test_signed_char.c */
#include
int main () {
char c = 255; // 我們以為char是無(wú)符號(hào)的,其范圍應(yīng)該是[0,255]
int i = 0;
int a[256];
for (i = 0; i < 256; i++) {
a[i] = 1;
}
printf("%d\n", c); // 我們期待輸出255
printf("%d\n", a[c]); // 我們期待輸出1
printf("%d\n", a[255]);
return 0;
}
gcc -Wchar-subscripts test_signed_char.c
test_signed_char.c: In function `main':
test_signed_char.c:13: warning: array subscript has type `char'
其輸出結(jié)果:
-1
-4197476
1
從輸出結(jié)果來看Solaris 9/gcc 3.2上char默認(rèn)實(shí)現(xiàn)類型為signed char;在Windows XP/gcc-3.4.2上也是一樣。
Windows上的輸出結(jié)果:
-1
16 (隨機(jī)值)
1
[-Wcomment]
當(dāng)'/*'出現(xiàn)在 '/* ... */'注釋中,或者'\'出現(xiàn)在'// ...'注釋結(jié)尾處時(shí),使用-Wcomment會(huì)給出警告。不要小覷這些馬虎代碼,它很可能會(huì)影響程序的運(yùn)行結(jié)果。如下面的例子:
e.g.
/*
* test_comment.c
* gcc -Wcomment test_comment.c
*/
#include
int main() {
int a = 1;
int b = 2;
int c = 0; // ok just test\
c = a + b;
/*
* 這里我們期待c = 3
* /* 但實(shí)際上輸出c = 0
*/
printf("the c is %d\n", c);
return 0;
}
gcc -Wcomment test_comment.c
test_comment.c:10:30: warning: multi-line comment
test_comment.c:15:12: warning: "/*" within comment
輸出:
the c is 0
[-Wformat]
檢查printf和scanf等格式化輸入輸出函數(shù)的格式字符串與參數(shù)類型的匹配情況,如果發(fā)現(xiàn)不匹配則發(fā)出警告。某些時(shí)候格式字符串與參數(shù)類型的不匹配會(huì)導(dǎo)致程序運(yùn)行錯(cuò)誤,所以這是個(gè)很有用的警告選項(xiàng)。
e.g.
/*
* test_format.c
*/
#include
int main() {
long l = 1;
double d = 55.67;
printf("%d\n", l);
printf("%d\n", d);
return 0;
}
gcc -Wformat test_format.c
test_format.c: In function `main':
test_format.c:10: warning: int format, long int arg (arg 2)
test_format.c:11: warning: int format, double arg (arg 2)
輸出:
1
1078711746
[-Wimplicit]
該警告選項(xiàng)實(shí)際上是-Wimplicit-int和-Wimplicit-function-declaration兩個(gè)警告選項(xiàng)的集合。前者在聲明函數(shù)卻未指明函數(shù)返回類型時(shí)給出警告,后者則是在函數(shù)聲明前調(diào)用該函數(shù)時(shí)給出警告。
e.g.
/*
* test_implicit.c
*/
#include
add(int a, int b) { //函數(shù)沒有聲明返回類型
return a + b;
}
int test() {
int a = 0;
int b = 0;
int c = 0;
int d = 0;
c = add(a, b);
d = sub(a, b); //未聲明sub的函數(shù)原型
return 0;
}
gcc -Wimplicit -c test_implicit.c
test_implicit.c:7: warning: return type defaults to `int'
test_implicit.c: In function `test':
test_implicit.c:18: warning: implicit declaration of function `sub'
[-Wmissing-braces]
當(dāng)聚合類型或者數(shù)組變量的初始化表達(dá)式?jīng)]有'充分'用括號(hào){}括起時(shí),給出警告。文字表述很難理解,舉例說明則清晰些??聪旅娴睦樱?/p>
e.g.
/*
* test_missing_braces.c
*/
struct point {
int x;
int y;
};
struct line {
struct point start;
struct point end;
};
typedef struct line line;
int main() {
int array1[2][2] = {11, 12, 13, 14};
int array2[2][2] = {{11, 12}, {13, 14}}; // ok
line l1 = {1, 1, 2, 2};
line l2 = {{2, 2}, {3, 3}}; // ok
return 0;
}
gcc -Wmissing-braces test_missing_braces.c
test_missing_braces.c: In function `main':
test_missing_braces.c:19: warning: missing braces around initializer
test_missing_braces.c:19: warning: (near initialization for `array1[0]')
test_missing_braces.c:21: warning: missing braces around initializer
test_missing_braces.c:21: warning: (near initialization for `l1.start')
[-Wparentheses]
這是一個(gè)很有用的警告選項(xiàng),它能幫助你從那些看起來語(yǔ)法正確但卻由于操作符優(yōu)先級(jí)或者代碼結(jié)構(gòu)'障眼'而導(dǎo)致錯(cuò)誤運(yùn)行的代碼中解脫出來。好長(zhǎng)的一個(gè)長(zhǎng)句,還是看例子理解吧!:)
e.g.
/*
* test_parentheses.c
* gcc -Wparentheses test_parentheses.c
*/
#include
int main() {
int a = 1;
int b = 1;
int c = 1;
int d = 1;
if (a && b || c) { // 人們很難記住邏輯操作符的操作順序,所以編譯器建議加上()
;
}
if (a == 12)
if (b)
d = 9;
else
d = 10; //從代碼的縮進(jìn)上來看,這句仿佛是if (a == 12)的else分支
printf("the d is %d\n", d); //期待d = 10, 而結(jié)果卻是1
return 0;
}
gcc -Wparentheses test_parentheses.c
test_parentheses.c: In function `main':
test_parentheses.c:13: warning: suggest parentheses around && within ||
test_parentheses.c:17: warning: suggest explicit braces to avoid ambiguous `else'
輸出:
the d is 1
[-Wsequence-point]
關(guān)于順序點(diǎn)(sequence point),在C標(biāo)準(zhǔn)中有解釋,不過很晦澀。我們?cè)谄綍r(shí)編碼中盡量避免寫出與實(shí)現(xiàn)相關(guān)、受實(shí)現(xiàn)影響的代碼便是了。而-Wsequence-point選項(xiàng)恰恰可以幫我們這個(gè)忙,它可以幫我們查出這樣的代碼來,并給出其警告。
e.g.
/*
* test_sequence_point.c
* gcc -Wsequence-point test_sequence_point.c
*/
#include
int main() {
int i = 12;
i = i--;
printf("the i is %d\n", i);
return 0;
}
gcc -Wsequence-point test_sequence_point.c
test_sequence_point.c: In function `main':
test_sequence_point.c:10: warning: operation on `i' may be undefined
在兩個(gè)平臺(tái)上給出的編譯警告都是一致的,但是輸出結(jié)果卻大相徑庭。
Solaris輸出:
the i is 11
Windows輸出:
the i is 12
類似的像這種與順序點(diǎn)相關(guān)的代碼例子有:
i = i++;
a[i] = b[i++]
a[i++] = i
等等...
[-Wswitch]
這個(gè)選項(xiàng)的功能淺顯易懂,通過文字描述也可以清晰的說明。當(dāng)以一個(gè)枚舉類型(enum)作為switch語(yǔ)句的索引時(shí)但卻沒有處理default情況,或者沒有處理所有枚舉類型定義范圍內(nèi)的情況時(shí),該選項(xiàng)會(huì)給處警告。
e.g.
/*
* test_switch1.c
*/
enum week {
SUNDAY,
MONDAY,
TUESDAY /* only an example , we omitted the others */
};
int test1() {
enum week w = SUNDAY;
switch(w) {
case SUNDAY:
break; // without default or the other case handlings
};
return 0;
}
int test2() { // Ok, won't invoke even a warning
enum week w = SUNDAY;
switch(w) {
case SUNDAY:
break;
default:
break;
};
return 0;
}
int test3() { // Ok, won't invoke even a warning
enum week w = SUNDAY;
switch(w) {
case SUNDAY:
break;
case MONDAY:
break;
case TUESDAY:
break;
};
return 0;
}
gcc -Wswitch -c test_switch.c
test_switch.c: In function `test1':
test_switch.c:16: warning: enumeration value `MONDAY' not handled in switch
test_switch.c:16: warning: enumeration value `TUESDAY' not handled in switch
[-Wunused]
-Wunused是-Wunused-function、-Wunused-label、-Wunused-variable、-Wunused-value選項(xiàng)的集合,-Wunused-parameter需單獨(dú)使用。
(1) -Wunused-function用來警告存在一個(gè)未使用的static函數(shù)的定義或者存在一個(gè)只聲明卻未定義的static函數(shù),參見下面例子中的func1和func2;
(2) -Wunused-label用來警告存在一個(gè)使用了卻未定義或者存在一個(gè)定義了卻未使用的label,參加下面例子中的func3和func7;
(3) -Wunused-variable用來警告存在一個(gè)定義了卻未使用的局部變量或者非常量static變量;參見下面例子中func5和var1;
(4) -Wunused-value用來警告一個(gè)顯式計(jì)算表達(dá)式的結(jié)果未被使用;參見下面例子中func6
(5) -Wunused-parameter用來警告一個(gè)函數(shù)的參數(shù)在函數(shù)的實(shí)現(xiàn)中并未被用到,參見下面例子中func4。
下面是一個(gè)綜合的例子
e.g.
/*
* test_unused.c
*/
static void func1(); //to prove function used but never defined
static void func2(); //to prove function defined but not used
static void func3(); //to prove label used but never defined
static void func7(); //to prove label defined but never used
static void func4(int a); //to prove parameter declared but not used
static void func5(); //to prove local variable defined but not used
static void func6(); //to prove value evaluated but not used
static int var1;
void test() {
func1();
func3();
func4(4);
func5();
func6();
}
static void func2() {
; // do nothing
}
static void func3() {
goto over;
}
static void func4(int a) {
; // do nothing
}
static void func5() {
int a = 0;
}
static void func6() {
int a = 0;
int b = 6;
a + b;
}
gcc -Wunused-parameter -c test_unused.c //如果不是用-Wunused-parameter,則func4函數(shù)將不被警告。
test_unused.c: In function `func3':
test_unused.c:30: label `over' used but not defined
test_unused.c: In function `func7':
test_unused.c:35: warning: deprecated use of label at end of compound statement
test_unused.c:34: warning: label `over' defined but not used
test_unused.c: In function `func4':
test_unused.c:37: warning: unused parameter `a'
test_unused.c: In function `func5':
test_unused.c:42: warning: unused variable `a'
test_unused.c: In function `func6':
test_unused.c:48: warning: statement with no effect
test_unused.c: At top level:
test_unused.c:6: warning: `func1' used but never defined
test_unused.c:25: warning: `func2' defined but not used
test_unused.c:14: warning: `var1' defined but not used
[-Wuninitialized]
該警告選項(xiàng)用于檢查一個(gè)局部自動(dòng)變量在使用之前是否已經(jīng)初始化了或者在一個(gè)longjmp調(diào)用可能修改一個(gè)non-volatile automatic variable時(shí)給出警告。目前編譯器還不是那么smart,所以對(duì)有些可以正確按照程序員的意思運(yùn)行的代碼還是給出警告。而且該警告選項(xiàng)需要和'-O'選項(xiàng)一起使用,否則你得不到任何uinitialized的警告。
e.g.
/*
* test_uninitialized.c
*/
int test(int y) {
int x;
switch (y) {
case 1:
x = 11;
break;
case 2:
x = 22;
break;
case 3:
x = 33;
break;
}
return x;
}
gcc -Wuninitialized -O -c test_uninitialized.c
test_uninitialized.c: In function `test':
test_uninitialized.c:6: warning: `x' might be used uninitialized in this function
2、非-Wall集合警告選項(xiàng)
以下討論的這些警告選項(xiàng)并不包含在-Wall中,需要程序員顯式添加。
[-Wfloat-equal]
該項(xiàng)用來檢查浮點(diǎn)值是否出現(xiàn)在相等比較的表達(dá)式中。
e.g.
/*
* test_float_equal.c
*/
void test(int i) {
double d = 1.5;
if (d == i) {
;
}
}
gcc -Wfloat-equal -c test_float_equal.c
test_float_equal.c: In function `test':
test_float_equal.c:8: warning: comparing floating point with == or != is unsafe
[-Wshadow]
當(dāng)局部變量遮蔽(shadow)了參數(shù)、全局變量或者是其他局部變量時(shí),該警告選項(xiàng)會(huì)給我們以警告信息。
e.g.
/*
* test_shadow.c
*/
int g;
void test(int i) {
short i;
double g;
}
gcc -Wshadow -c test_shadow.c
test_shadow.c: In function `test':
test_shadow.c:9: warning: declaration of `i' shadows a parameter
test_shadow.c:10: warning: declaration of `g' shadows a global declaration
test_shadow.c:6: warning: shadowed declaration is here
[-Wbad-function-cast]
當(dāng)函數(shù)(準(zhǔn)確地說應(yīng)該是函數(shù)返回類型)被轉(zhuǎn)換為非匹配類型時(shí),均產(chǎn)生警告。
e.g.
/*
* test_bad_func_case.c
*/
int add(int a, int b) {
return a+b;
}
void test() {
char *p = (char*)add(1, 13);
}
gcc -Wbad-function-cast -c test_bad_func_case.c
test_bad_func_case.c: In function `test':
test_bad_func_case.c:11: warning: cast does not match function type
[-Wcast-qual]
當(dāng)去掉修飾源Target的限定詞(如const)時(shí),給出警告。
e.g.
/*
* test_cast_qual.c
*/
void test() {
char c = 0;
const char *p = &c;
char *q;
q = (char*)p;
}
gcc -Wcast-qual -c test_cast_qual.c
test_cast_qual.c: In function `test':
test_cast_qual.c:10: warning: cast discards qualifiers from pointer target type
[-Wcast-align]
這是個(gè)非常有用的選項(xiàng),特別是對(duì)于在Solaris這樣的對(duì)內(nèi)存對(duì)齊校驗(yàn)的平臺(tái)尤其重要。它用于在從對(duì)齊系數(shù)小的地址(如char*)轉(zhuǎn)換為對(duì)齊系數(shù)大的地址(如int*)轉(zhuǎn)換時(shí)給出警告。
e.g.
/*
* test_cast_align.c
*/
#include
int main() {
char c = 1;
char *p = &c; //ok
int *q = (int*)p; //bad align-cast
printf("the *q is %d\n", *q);
return 0;
}
gcc -Wcast-align test_cast_align.c
test_cast_align.c: In function `main':
test_cast_align.c:9: warning: cast increases required alignment of target type
輸出:
總線錯(cuò)誤 ((主存儲(chǔ)器)信息轉(zhuǎn)儲(chǔ)) //on Solaris 9
[-Wsign-compare]
在有符號(hào)數(shù)和無(wú)符號(hào)數(shù)進(jìn)行值比較時(shí),有符號(hào)數(shù)可能在比較之前被轉(zhuǎn)換為無(wú)符號(hào)數(shù)而導(dǎo)致結(jié)果錯(cuò)誤。使用該選項(xiàng)會(huì)對(duì)這樣的情況給出警告。
e.g.
/*
* test_sign_compare.c
*/
#include
int main() {
unsigned int i = 128;
signed int j = -1;
if (i < j) {
printf("i < j\n");
} else {
printf("i > j\n");
}
return 0;
}
gcc -Wsign-compare test_sign_compare.c
test_sign_compare.c: In function `main':
test_sign_compare.c:10: warning: comparison between signed and unsigned
輸出:
i < j
[-Waggregate-return]
如果一個(gè)函數(shù)返回一個(gè)聚合類型,如結(jié)構(gòu)體、聯(lián)合或者數(shù)組,該選項(xiàng)就會(huì)給出警告信息。較簡(jiǎn)單不舉例了。
[-Wmultichar]
當(dāng)我們寫下如此代碼時(shí):char c = 'peter', 使用該選項(xiàng)會(huì)給出警告。這個(gè)選項(xiàng)是默認(rèn)選項(xiàng),你無(wú)需單獨(dú)使用該選項(xiàng),不過你可以使用-Wno-multichar來關(guān)閉這些警告信息,但是這可是不建議你去做的。對(duì)于char c = 'peter'這樣的代碼的處理是與平臺(tái)相關(guān),不可移植的。
e.g.
/*
* test_multichar.c
*/
int main() {
char c = 'peter';
printf("c is %c\n", c);
return 0;
}
但這里在Windows和Solaris平臺(tái)輸出的結(jié)果卻一致:
c is r
[-Wunreachable-code]
這個(gè)選項(xiàng)是一個(gè)檢查冗余代碼或疏忽代碼好辦法。它一旦檢查到你的代碼中有不可達(dá)的代碼,就會(huì)發(fā)出警告。這些代碼往往會(huì)存在潛在的危機(jī)。
e.g.
/*
* test_unreachable.c
*/
int test(char c) {
if (c < 256) {
return 0;
} else {
return 1;
}
}
gcc -Wunreachable-code -c test_unreachable.c
test_unreachable.c: In function `test':
test_unreachable.c:6: warning: comparison is always true due to limited range of data type
test_unreachable.c:9: warning: will never be executed
[-Wconvertion]
由于原型定義而引起的定點(diǎn)和浮點(diǎn)數(shù)之間的隱式轉(zhuǎn)換(強(qiáng)制轉(zhuǎn)換)或者由有符號(hào)數(shù)和無(wú)符號(hào)數(shù)之間隱式轉(zhuǎn)換轉(zhuǎn)換引起的警告。
e.g.
/*
* test_conversion.c
*/
#include
void getdouble(double d) {
; // do nothing
}
int main() {
unsigned int k;
int n = 12;
k = -1;
k = (unsigned int)-1; // ok, explicit conversion ,no warning
getdouble(n);
return 0;
}
gcc -Wconversion test_conversion.c
test_conversion.c: In function `main':
test_conversion.c:15: warning: negative integer implicitly converted to unsigned type
test_conversion.c:18: warning: passing arg 1 of `getdouble' as floating rather than integer due to prototype
3、-Wtraditional和-W
這兩個(gè)警告選項(xiàng)其實(shí)也都是一些組合(大部分都在上面提到過),前者用來在代碼中使用了標(biāo)準(zhǔn)C不同于傳統(tǒng)C的特性時(shí),發(fā)出警告;后者也是針對(duì)一些事件打開一個(gè)警告集合。關(guān)于它們的說明具體可參見'Using the GNU Compiler Collection'。
[注1]
本文中的例子的測(cè)試環(huán)境為Solaris 9 SPARC平臺(tái),GCC-3.2和Windows XP Intel x86平臺(tái),mingw32 gcc3.4.2,如無(wú)特殊差異,所有注釋均針對(duì)這兩個(gè)測(cè)試環(huán)境。
轉(zhuǎn)載自:http://hi.baidu.com/qfktxxtuakbostq/item/5902fbb5d3d61ae84fc7fd99