标签 C 下的文章

GCC警告选项例解

程序员是追求完美的一族,即使是一般的程序员大多也都不想看到自己的程序中有甚至那么一点点的瑕疵。遇到任意一条编译器警告都坚决不放过。有人会说:我们可以使用比编译器更加严格的静态代码检查工具,如splint。这个建议也很不错。不过lint工具使用起来较繁琐,有时候还需要记住一些特定符号并插入到你自己的代码中才行,门槛较高,这也让很多人止步于此。那么我们就从此放弃么?不,如今的编译器做得都很好,它可以帮助我们的找到绝大多数可能出现问题的代码,前提是你要学会控制编译器去找到这些问题代码,而熟悉编译器的警告选项恰恰是体现控制力的好方法。当你可以自如控制编译器警告输出的时候,你就算是'入道'了,同时你对语言的理解也更进一步了。

有人说:我就是用一个-Wall选项就可以了,一般选手可以这么做,而且他可以不知道-Wall会跟踪哪些类型的问题;但是高级选手是不会只使用-Wall的,他会把每条警告都研究的很透彻,会在Makefile中列出他想让编译器输出哪些类型的警告以替代-Wall,他会屏蔽掉那些对他的代码'毫无用处'的警告(很可能他使用了编译器对语言的扩展功能),他会有个和编译器交流的过程。

俗话说:'工欲善其事,必先利其器',一直在工作中使用GNU C编译器(以下简称GCC),这里对GCC的一些警告选项细致的分析,并列举几个简单的例子[注1]供分析参考。

1. -Wall集合警告选项
我们平时可能大多数情况只使用-Wall编译警告选项,实际上-Wall选项是一系列警告编译选项的集合。下面逐一分析这一集合中的各个选项:

[-Wchar-subscripts]
如果数组使用char类型变量做为下标值的话,则发出警告。因为在某些平台上char可能默认为signed char,一旦溢出,就可能导致某些意外的结果。

e.g.
/* test_signed_char.c */
#include

int main () {
        char    c       = 255; // 我们以为char是无符号的,其范围应该是[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][/c][/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'

其输出结果:
-1
-4197476
1
从输出结果来看Solaris 9/gcc 3.2上char默认实现类型为signed char;在Windows XP/gcc-3.4.2上也是一样。
Windows上的输出结果:
-1
16 (随机值)
1

[-Wcomment]
当'/*'出现在 '/* … */'注释中,或者'\'出现在'// …'注释结尾处时,使用-Wcomment会给出警告。不要小觑这些马虎代码,它很可能会影响程序的运行结果。如下面的例子:

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
         * /* 但实际上输出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等格式化输入输出函数的格式字符串与参数类型的匹配情况,如果发现不匹配则发出警告。某些时候格式字符串与参数类型的不匹配会导致程序运行错误,所以这是个很有用的警告选项。

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]
该警告选项实际上是-Wimplicit-int和-Wimplicit-function-declaration两个警告选项的集合。前者在声明函数却未指明函数返回类型时给出警告,后者则是在函数声明前调用该函数时给出警告。

e.g.
/*
 * test_implicit.c
 */
#include

add(int a, int b) { //函数没有声明返回类型
        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的函数原型
        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]
当聚合类型或者数组变量的初始化表达式没有'充分'用括号{}括起时,给出警告。文字表述很难理解,举例说明则清晰些。看下面的例子:

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]
这是一个很有用的警告选项,它能帮助你从那些看起来语法正确但却由于操作符优先级或者代码结构'障眼'而导致错误运行的代码中解脱出来。好长的一个长句,还是看例子理解吧!:)

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; //从代码的缩进上来看,这句仿佛是if (a == 12)的else分支

        printf("the d is %d\n", d); //期待d = 10, 而结果却是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]
关于顺序点(sequence point),在C标准中有解释,不过很晦涩。我们在平时编码中尽量避免写出与实现相关、受实现影响的代码便是了。而-Wsequence-point选项恰恰可以帮我们这个忙,它可以帮我们查出这样的代码来,并给出其警告。

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

在两个平台上给出的编译警告都是一致的,但是输出结果却大相径庭。

Solaris输出:
the i is 11

Windows输出:
the i is 12

类似的像这种与顺序点相关的代码例子有:
i = i++;
a[i] = b[i++]
a[i++] = i
等等…

[-Wswitch]
这个选项的功能浅显易懂,通过文字描述也可以清晰的说明。当以一个枚举类型(enum)作为switch语句的索引时但却没有处理default情况,或者没有处理所有枚举类型定义范围内的情况时,该选项会给处警告。

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选项的集合,-Wunused-parameter需单独使用。
(1) -Wunused-function用来警告存在一个未使用的static函数的定义或者存在一个只声明却未定义的static函数,参见下面例子中的func1和func2;
(2) -Wunused-label用来警告存在一个使用了却未定义或者存在一个定义了却未使用的label,参加下面例子中的func3和func7;
(3) -Wunused-variable用来警告存在一个定义了却未使用的局部变量或者非常量static变量;参见下面例子中func5和var1;
(4) -Wunused-value用来警告一个显式计算表达式的结果未被使用;参见下面例子中func6
(5) -Wunused-parameter用来警告一个函数的参数在函数的实现中并未被用到,参见下面例子中func4。

下面是一个综合的例子
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函数将不被警告。
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]
该警告选项用于检查一个局部自动变量在使用之前是否已经初始化了或者在一个longjmp调用可能修改一个non-volatile automatic variable时给出警告。目前编译器还不是那么smart,所以对有些可以正确按照程序员的意思运行的代码还是给出警告。而且该警告选项需要和'-O'选项一起使用,否则你得不到任何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集合警告选项
以下讨论的这些警告选项并不包含在-Wall中,需要程序员显式添加。

[-Wfloat-equal]
该项用来检查浮点值是否出现在相等比较的表达式中。

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]
当局部变量遮蔽(shadow)了参数、全局变量或者是其他局部变量时,该警告选项会给我们以警告信息。

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]
当函数(准确地说应该是函数返回类型)被转换为非匹配类型时,均产生警告。

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]
当去掉修饰源Target的限定词(如const)时,给出警告。

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]
这是个非常有用的选项,特别是对于在Solaris这样的对内存对齐校验的平台尤其重要。它用于在从对齐系数小的地址(如char*)转换为对齐系数大的地址(如int*)转换时给出警告。

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

输出:
总线错误 ((主存储器)信息转储) //on Solaris 9

[-Wsign-compare]
在有符号数和无符号数进行值比较时,有符号数可能在比较之前被转换为无符号数而导致结果错误。使用该选项会对这样的情况给出警告。

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]
如果一个函数返回一个聚合类型,如结构体、联合或者数组,该选项就会给出警告信息。较简单不举例了。

[-Wmultichar]
当我们写下如此代码时:char c = 'peter', 使用该选项会给出警告。这个选项是默认选项,你无需单独使用该选项,不过你可以使用-Wno-multichar来关闭这些警告信息,但是这可是不建议你去做的。对于char c = 'peter'这样的代码的处理是与平台相关,不可移植的。

e.g.
/*
 * test_multichar.c
 */
int main() {
        char c = 'peter';
        printf("c is %c\n", c);
        return 0;
}
但这里在Windows和Solaris平台输出的结果却一致:
c is r

[-Wunreachable-code]
这个选项是一个检查冗余代码或疏忽代码好办法。它一旦检查到你的代码中有不可达的代码,就会发出警告。这些代码往往会存在潜在的危机。

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]
由于原型定义而引起的定点和浮点数之间的隐式转换(强制转换)或者由有符号数和无符号数之间隐式转换转换引起的警告。

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
这两个警告选项其实也都是一些组合(大部分都在上面提到过),前者用来在代码中使用了标准C不同于传统C的特性时,发出警告;后者也是针对一些事件打开一个警告集合。关于它们的说明具体可参见'Using the GNU Compiler Collection'。

[注1]
本文中的例子的测试环境为Solaris 9 SPARC平台,GCC-3.2和Windows XP Intel x86平台,mingw32 gcc3.4.2,如无特殊差异,所有注释均针对这两个测试环境。

Hacker Culture摘要

最近看了Eric S. Raymond的被称为开源文化圣典的'Cathedral and Bazaar'(大教堂与市集)以及他的另外一篇文章'How To Become A Hacker',必须承认的是我不能够完全理解其中的内容,因为没有体验,或者说我还不够资格对Hacker Culture高谈阔论,所以这里仅作部分摘要,并说说自己第一时间的感受,望日后能温故知新。

在开始了解Hacker Culture之前我们应该知道'什么是Hacker'。Hacker不同于Cracker,前者指那些热衷于计算机技术,水平高超的电脑专家,他们把通过自己的实践而获得的知识广泛传播;而后者则尤指那些为了个人利益利用计算机技术搞非法破坏的人。像我们耳熟能详的Hacker先驱包括开源软件运动的发起人Richard M. Stallman、Unix之父Ken Thompson、C语言的发明人之一的Dennis Ritchie、Linux之父Linus Torvalds以及Eric S. Raymond等等。我相信这些人才是从事计算机行业的人们心目中真正的'Hero'。

'Cathedral and Bazaar'可谓是开源世界对Hacker Culture的一个阶段性的小结,当然Hacker Culture还在进化,其内容也在不断的丰富当中。下面是从'Cathedral and Bazaar'摘录的一些我觉得能够代表Hacker Culture的语句:

1.Every good work of software starts by scratching a developer's personal itch.
这里有一个生僻词itch,这个词有'发痒'、'渴望'的意思。这句可理解为“每个好的软件工作都开始于满足开发者个人的渴望或为开发者个人'抓痒'”。Unix的起缘可以很好地证明这一点。而现在的大多数商业软件的开发者则不能归为此类,原因不讲自明。

2. Good programmers know what to write. Great ones know what to rewrite (and reuse).
Linus之所以能独立完成一个操作系统内核,很大原因是因为他没有'从头开始',而是利用已有的优秀设计思想。

3. When you lose interest in a program, your last duty to it is to hand it off to a competent successor.
Hacker也要'能上能下'。^_^

4. Treating your users as co-developers is your least-hassle route to rapid code improvement and effective debugging.
把用户当作协作开发者。

5. Release Early, Release Often
这与4相辅相成,互利互惠。Linux已经展现给我们一个Best实践,其“早发布、常发布策略”的一个效果就是利用快速的传播反馈修订来使重复劳动达到最小。

6. Smart data structures and dumb code works a lot better than the other way around.
优秀的数据结构设计总是至关重要的,在平时的开发中这一点体会破深。Brooks曾幽默地说:"Show me your [code] and conceal your [data structures], and I shall continue to be mystified. Show me your [data structures], and I won’t usually need your [code]; it'll be obvious."

7. Often, the most striking and innovative solutions come from realizing that your concept of the problem was wrong.
当你认识到你对问题的理解是错误的,这时不要灰心,因为一个具有革新性的解决方案也许正摆在你的眼前,我想很多人都有过类似的经历,Me,too。

8. "Perfection (in design) is achieved not when there is nothing more to add, but rather when there is nothing more to take away."
是不是颠覆了你以前对好的设计的理解了呢?

9. To solve an interesting problem, start by finding a problem that is interesting to you.
趣之所在,力之所在。

10. Software is developed for peer recognition not for money.
至高无上的境界,不为'铜臭'打工。

这里再列出一条,这是在一位同行给Eric的回复中提到的一条:"杀掉一个项目最快的方法是在你什么都还没有之前就宣布它,我已经见的太多了,尤其是在Linux世界里",看到这一条相信很多曾组织或参与开源项目的人都会深刻的体会到,Me , too。

中国程序员在开源软件世界中的地位大家也都略知一二,我想这或多或少都与我们对Hacker Culture的理解有关。理解和认同'Hacker Culture'是你进入开源世界的第一步,正所谓思想的融入才是真正的融入。

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言精进之路1 Go语言精进之路2 Go语言编程指南
商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



Statcounter View My Stats