俗话说:'工欲善其事,必先利其器',一直在工作中使用GNU C编译器(以下简称GCC),这里对GCC的一些警告选项细致的分析,并列举几个简单的例子[注1]供分析参考。
1、 -Wall集合警告选项我们平时可能大多数情况只使用-Wall编译警告选项,实际上-Wall选项是一系列警告编译选项的集合。下面逐一分析这一集合中的各个选项:
1.1 [-Wchar-subscripts]
[-Wchar-subscripts]如果数组使用char类型变量做为下标值的话,则发出警告。因为在某些平台上char可能默认为signed char,一旦溢出,就可能导致某些意外的结果。
- /* test_signed_char.c */
- #include<stdio.h>
-
- 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]); // 我们期待输出1
-
- printf("%d\n", a[255]);
- return 0;
- }
gcc -Wchar-subscripts test_signed_char.ctest_signed_char.c: In function `main':test_signed_char.c:13: warning: array subscript has type `char'
其输出结果:-1-41974761从输出结果来看Solaris 9/gcc 3.2上char默认实现类型为signed char;在Windows XP/gcc-3.4.2上也是一样。Windows上的输出结果:-116 (随机值)1
1.2 [-Wcomment]
[-Wcomment]当'/*'出现在 '/* ... */'注释中,或者'\'出现在'// ...'注释结尾处时,使用-Wcomment会给出警告。不要小觑这些马虎代码,它很可能会影响程序的运行结果。如下面的例子:
- /* test_comment.c * gcc -Wcomment test_comment.c */
- #include<stdio.h>
-
- 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.ctest_comment.c:10:30: warning: multi-line commenttest_comment.c:15:12: warning: "/*" within comment
输出:the c is 0
1.3 [-Wformat]
[-Wformat]检查printf和scanf等格式化输入输出函数的格式字符串与参数类型的匹配情况,如果发现不匹配则发出警告。某些时候格式字符串与参数类型的不匹配会导致程序运行错误,所以这是个很有用的警告选项。
- /* test_format.c */
- #include<stdio.h>
-
- int main() {
- long l = 1;
- double d = 55.67;
-
- printf("%d\n", l);
- printf("%d\n", d);
-
- return 0;
- }
gcc -Wformat test_format.ctest_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)
输出:11078711746
1.4 [-Wimplicit]
[-Wimplicit]该警告选项实际上是-Wimplicit-int和-Wimplicit-function-declaration两个警告选项的集合。前者在声明函数却未指明函数返回类型时给出警告,后者则是在函数声明前调用该函数时给出警告。
- /* test_implicit.c */
- #include<stdio.h>
-
- 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.ctest_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'
1.5 [-Wmissing-braces]
[-Wmissing-braces]当聚合类型或者数组变量的初始化表达式没有'充分'用括号{}括起时,给出警告。文字表述很难理解,举例说明则清晰些。看下面的例子:
- /* 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.ctest_missing_braces.c: In function `main':test_missing_braces.c:19: warning: missing braces around initializertest_missing_braces.c:19: warning: (near initialization for `array1[0]')test_missing_braces.c:21: warning: missing braces around initializertest_missing_braces.c:21: warning: (near initialization for `l1.start')
1.6 [-Wparentheses]
[-Wparentheses]这是一个很有用的警告选项,它能帮助你从那些看起来语法正确但却由于操作符优先级或者代码结构'障眼'而导致错误运行的代码中解脱出来。好长的一个长句,还是看例子理解吧!:)
- /* test_parentheses.c * gcc -Wparentheses test_parentheses.c */
- #include<stdio.h>
-
-
- 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.ctest_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
1.7 [-Wsequence-point]
[-Wsequence-point]关于顺序点(sequence point),在C标准中有解释,不过很晦涩。我们在平时编码中尽量避免写出与实现相关、受实现影响的代码便是了。而-Wsequence-point选项恰恰可以帮我们这个忙,它可以帮我们查出这样的代码来,并给出其警告。
- /* test_sequence_point.c * gcc -Wsequence-point test_sequence_point.c */
-
- #include<stdio.h>
-
- int main() {
- int i = 12;
- i = i--;
- printf("the i is %d\n", i);
- return 0;
- }
gcc -Wsequence-point test_sequence_point.ctest_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等等...
1.7 [-Wswitch]
[-Wswitch]这个选项的功能浅显易懂,通过文字描述也可以清晰的说明。当以一个枚举类型(enum)作为switch语句的索引时但却没有处理default情况,或者没有处理所有枚举类型定义范围内的情况时,该选项会给处警告。
- /* 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.ctest_switch.c: In function `test1':test_switch.c:16: warning: enumeration value `MONDAY' not handled in switchtest_switch.c:16: warning: enumeration value `TUESDAY' not handled in switch
1.8 [-Wunused]
[-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 definedstatic void func2(); //to prove function defined but not usedstatic void func3(); //to prove label used but never definedstatic void func7(); //to prove label defined but never usedstatic void func4(int a); //to prove parameter declared but not usedstatic void func5(); //to prove local variable defined but not usedstatic 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 definedtest_unused.c: In function `func7':test_unused.c:35: warning: deprecated use of label at end of compound statementtest_unused.c:34: warning: label `over' defined but not usedtest_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 effecttest_unused.c: At top level:test_unused.c:6: warning: `func1' used but never definedtest_unused.c:25: warning: `func2' defined but not usedtest_unused.c:14: warning: `var1' defined but not used
1.9 [-Wuninitialized]
[-Wuninitialized]该警告选项用于检查一个局部自动变量在使用之前是否已经初始化了或者在一个longjmp调用可能修改一个non-volatile automatic variable时给出警告。目前编译器还不是那么smart,所以对有些可以正确按照程序员的意思运行的代码还是给出警告。而且该警告选项需要和'-O'选项一起使用,否则你得不到任何uinitialized的警告。
- /* 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.ctest_uninitialized.c: In function `test':test_uninitialized.c:6: warning: `x' might be used uninitialized in this function
2、非-Wall集合警告选项以下讨论的这些警告选项并不包含在-Wall中,需要程序员显式添加。
2.1 [-Wfloat-equal]
[-Wfloat-equal]该项用来检查浮点值是否出现在相等比较的表达式中。
- /* test_float_equal.c */
-
- void test(int i) {
- double d = 1.5;
- if (d == i) {
- ;
- }
- }
gcc -Wfloat-equal -c test_float_equal.ctest_float_equal.c: In function `test':test_float_equal.c:8: warning: comparing floating point with == or != is unsafe
2.2 [-Wshadow]
[-Wshadow]当局部变量遮蔽(shadow)了参数、全局变量或者是其他局部变量时,该警告选项会给我们以警告信息。
- /* test_shadow.c */
- int g;
-
- void test(int i) {
- short i;
- double g;
- }
gcc -Wshadow -c test_shadow.ctest_shadow.c: In function `test':test_shadow.c:9: warning: declaration of `i' shadows a parametertest_shadow.c:10: warning: declaration of `g' shadows a global declarationtest_shadow.c:6: warning: shadowed declaration is here
2.3 [-Wbad-function-cast]
[-Wbad-function-cast]当函数(准确地说应该是函数返回类型)被转换为非匹配类型时,均产生警告。
- /* 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.ctest_bad_func_case.c: In function `test':test_bad_func_case.c:11: warning: cast does not match function type
2.4 [-Wcast-qual]
[-Wcast-qual]当去掉修饰源Target的限定词(如const)时,给出警告。
- /* 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.ctest_cast_qual.c: In function `test':test_cast_qual.c:10: warning: cast discards qualifiers from pointer target type
2.5 -Wcast-align]
[-Wcast-align]这是个非常有用的选项,特别是对于在Solaris这样的对内存对齐校验的平台尤其重要。它用于在从对齐系数小的地址(如char*)转换为对齐系数大的地址(如int*)转换时给出警告。
- /* test_cast_align.c */
- #include<stdio.h>
-
- 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.ctest_cast_align.c: In function `main':test_cast_align.c:9: warning: cast increases required alignment of target type
输出:总线错误 ((主存储器)信息转储) //on Solaris 9
2.6 [-Wsign-compare]
[-Wsign-compare]在有符号数和无符号数进行值比较时,有符号数可能在比较之前被转换为无符号数而导致结果错误。使用该选项会对这样的情况给出警告。
- /* test_sign_compare.c */
- #include<stdio.h>
-
- 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.ctest_sign_compare.c: In function `main':test_sign_compare.c:10: warning: comparison between signed and unsigned
输出:i < j
2.7 [-Waggregate-return]
[-Waggregate-return]如果一个函数返回一个聚合类型,如结构体、联合或者数组,该选项就会给出警告信息。较简单不举例了。
[-Wmultichar]当我们写下如此代码时:char c = 'peter', 使用该选项会给出警告。这个选项是默认选项,你无需单独使用该选项,不过你可以使用-Wno-multichar来关闭这些警告信息,但是这可是不建议你去做的。对于char c = 'peter'这样的代码的处理是与平台相关,不可移植的。
- /* test_multichar.c */
- int main() {
- char c = 'peter';
- printf("c is %c\n", c);
- return 0;
- }
但这里在Windows和Solaris平台输出的结果却一致:c is r
2.8 [-Wunreachable-code]
[-Wunreachable-code]这个选项是一个检查冗余代码或疏忽代码好办法。它一旦检查到你的代码中有不可达的代码,就会发出警告。这些代码往往会存在潜在的危机。
- /* test_unreachable.c */
- int test(char c) {
- if (c < 256) {
- return 0;
- } else {
- return 1;
- }
- }
gcc -Wunreachable-code -c test_unreachable.ctest_unreachable.c: In function `test':test_unreachable.c:6: warning: comparison is always true due to limited range of data typetest_unreachable.c:9: warning: will never be executed
2.9 [-Wconversion]
[-Wconversion]由于原型定义而引起的定点和浮点数之间的隐式转换(强制转换)或者由有符号数和无符号数之间隐式转换转换引起的警告。
- /* test_conversion.c */
- #include<stdio.h>
-
- 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.ctest_conversion.c: In function `main':test_conversion.c:15: warning: negative integer implicitly converted to unsigned typetest_conversion.c:18: warning: passing arg 1 of `getdouble' as floating rather than integer due to prototype
-Wtraditional和-W这两个警告选项其实也都是一些组合(大部分都在上面提到过),前者用来在代码中使用了标准C不同于传统C的特性时,发出警告;后者也是针对一些事件打开一个警告集合。关于它们的说明具体可参见'Using the GNU Compiler Collection'。
[注1]本文中的例子的测试环境为Solaris 9 SPARC平台,GCC-3.2和Windows XP Intel x86平台,mingw32 gcc3.4.2,如无特殊差异,所有注释均针对这两个测试环境。
3、关于内联函数支持
大家都知道,在程序中,通过把一个函数声明为内联(inline)函数,就可以让gcc把函数的代码集成(嵌入)到调用该函数的代码中去。这样处理可以去掉函数调用时进入/退出时间开销,从而肯定能够加快执行速度。因此把一个函数声明为内联函数的主要目的就是能够尽量快速地执行函数体。
在gcc中,如果内联函数中有常数值,那么在编译期间gcc就可能用它来进行一些简化操作,因此并非所有内联函数的代码都会被嵌入到调用代码处。内联函数嵌入调用者代码中的操作是一种优化操作,因此只有进行优化编译时才会执行代码的嵌入。若编译过程中没有打开优化选项 "-O",那么内联函数的代码就不会被真正地嵌入到调用者代码中,而是作为普通函数来处理。
在gcc-4.1的手册中指示,需要使用一定的优化级别才能开启某些优化选项,针对内联函数的优化选项主要有:
'-fno-inline' 忽略代码中的 inline 关键字,该选项使编译器将内联函数以普通函数对待;等同无优化选项时的处理'-finline-functions' 编译器尝试将'简单'函数集成到调用代码处;如果所有对该函数的调用都被替换而集成在调用者代码中,而且该函数使用static声明了,则该函数就不再像平常那样被编译成汇编代码。具体什么方式,需要查询。必须在-O3选项下才开启'-fearly-inlining' 加速编译 默认可用'-finline-limit=N' gcc默认限制内联函数的大小,使用该选项可以控制内联函数的大小;默认值是600,可以设置如下几个值: max-inline-insns-single N/2 max-inline-insns-auto N/2 min-inline-insns 130 or N/4 max-inline-insns-rtl N
'-fkeep-inline-functions' 将声明为static以及inline的函数放进目标文件中,即使所有对该函数的调用都被集成在调用者代码中;该选项不影响使用extern inline声明的内联函数,该声明属于GNU c扩展。
声明一个函数为内联函数的方法:
- inline int func(int *a){
- (*a)++;
- }
函数中的某些语句用法可能会使得内联函数的替换操作无法正常进行,或者不适合进行替换操作。例如使用了可变参数,内存分配函数 malloc(),可变长度数据类型变量,非局部 goto语句以及递归函数。编译时可以使用选项 -Winline 让 gcc 对标志成 inline 但不能被替换的函数给出警告信息以及不能替换的原因。如下面例子,它使用了可变长度数据类型变量作为参数:
- inline int func(int *a){
- int c = 4;
- char p[c]; /* 可变长度数组 */
- (*a)++;
- }/* 测试函数: 使用 gcc -Winline ... 来提示信息 */
-
- int main(void){
- int d = 1;
- func(&d);
- return 0;
- }
假定存储文件为inline.c, gcc -O -Winline -c -o inline.o inline.c 来编译,这里 -O 选项必须打开,否则将没有警告信息输出,因为该函数会被当作普通函数来处理;警告信息如下:
inline.c: In function ‘func’:inline.c:2: warning: function ‘func’ can never be inlined because it uses alloca (override using the always_inline attribute)inline.c: In function ‘main’:inline.c:2: warning: inlining failed in call to ‘func’: function not inlinableinline.c:11: warning: called from here
可以看出它覆盖了 always_inline 属性,其它无法内联的的用法大家可以自己编写代码测试。 当在一个函数定义中既使用 inline 又使用 static 关键字时,那么如果所有对该内联函数的调用都被替换而集成在调用者代码中,并且程序中没有引用过该内联函数的地址,则该内联函数自身的汇编代码就不会被引用。这时,除非在编译过程中使用选项'-fkeep-inline-functions',否则 gcc 就不会再为该内联函数自身生成实际的汇编代码。由于某些原因,一些对内联函数的调用并不能被集成到函数中去。特别是在内联函数定义之前的调用语句是不会被替换集成的,并且也都不能是递归定义的函数。如果存在一个不能被替换集成的调用,那么内联函数就会像普通函数一样被编译成汇编代码,对于程序中有引用该内联函数的地址的处理同样无法集成。
对于上面这句话的理解,同样我们可以使用上面的 inline.c 函数来测试,当没有加上 static 关键字的时候,可以使用gcc -O -Winline -S -o inline.s inline.c 来生成汇编程序,可以看到 func 内联函数在汇编代码中同样被生成汇编代码而且被声明为函数;修改 inline.c 增加 static 关键字,gcc -O -Winline -S -o inline2.s inline.c 比较两个文件可以看到 inline2.s 中只有 main 符号,func 的代码直接被集成到 main 中了,此时如果想产生和没有加 static时的效果,编译时就要加上选项 '-fkeep-inline-functions';但是在 C++ 中,该选项会生成一个弱".weak"函数,也就是单独的汇编代码,若不加该选项,内联函数语义等同于 ISO C99 的语义,也就是都不单独生成汇编代码。
/* 不生成单独的汇编代码的版本,或者替换 static 为 extern */static inline int func(int *a){/* int c = 4; char p[c]; /* 可变长度数组 */*/ (*a)++;}
如果内联函数定义时没有使用 static,那么 gcc 就会假设其它程序文件中也对这个函数有调用。因为一个全局符号只能被定义一次,所以该函数就不能在其它源文件中再进行定义。因此这里对内联函数的调用就不能被替换集成。所以,一个非静态的内联函数总是会被编译出自己的汇编代码来。另外,ISO 标准 C99 中对不使用 static 关键字的内联函数定义等同于这里使用 static 的语义,但是为了保持兼容,最好还是明确指定 static 关键字。
如果在定义一个函数时还指定了 inline 和 extern 关键词,那么该函数定义仅用于内联集成,并且在任何情况下都不会单独产生该函数自身的汇编代码,即使明确引用了该函数的地址也不会产生。这样的一个地址会变成一个外部引用,就好像你仅仅声明了函数而没有定义函数一样。这种用法几乎等同于一个宏定义
4 、关于编译优化
gcc默认提供了5级优化选项的集合:
-O0:无优化(默认)
-O和-O1:使用能减少目标文件大小以及执行时间并且不会使编译时间明显增加的优化.在编译大型程序的时候会显著增加编译时内存的使用.
-O2:包含-O1的优化并增加了不需要在目标文件大小和执行速度上进行折衷的优化.编译器不执行循环展开以及函数内联.此选项将增加编译时间和目标文件的执行性能.
-Os:专门优化目标文件大小,执行所有的不增加目标文件大小的-O2优化选项.并且执行专门减小目标文件大小的优化选项.
-O3:打开所有-O2的优化选项并且增加 -finline-functions, -funswitch-loops,-fpredictive-commoning, -fgcse-after-reload and -ftree-vectorize优化选项.
-O1包含的选项-O1通常可以安全的和调试的选项一起使用:
-fauto-inc-dec -fcprop-registers -fdce -fdefer-pop -fdelayed-branch
-fdse -fguess-branch-probability -fif-conversion2 -fif-conversion
-finline-small-functions -fipa-pure-const -fipa-reference
-fmerge-constants -fsplit-wide-types -ftree-ccp -ftree-ch
-ftree-copyrename -ftree-dce -ftree-dominator-opts -ftree-dse
-ftree-fre -ftree-sra -ftree-ter -funit-at-a-time
以下所有的优化选项需要在名字前加上-f,如果不需要此选项可以使用-fno-前缀
defer-pop:延迟到只在必要时从函数参数栈中pop参数;
thread-jumps:使用跳转线程优化,避免跳转到另一个跳转;
branch-probabilities:分支优化;
cprop-registers:使用寄存器之间copy-propagation传值;
guess-branch-probability:分支预测;
omit-frame-pointer:可能的情况下不产生栈帧;
-O2:以下是-O2在-O1基础上增加的优化选项:
-falign-functions -falign-jumps -falign-loops -falign-labels
-fcaller-saves -fcrossjumping -fcse-follow-jumps -fcse-skip-blocks
-fdelete-null-pointer-checks -fexpensive-optimizations -fgcse
-fgcse-lm -foptimize-sibling-calls -fpeephole2 -fregmove
-freorder-blocks -freorder-functions -frerun-cse-after-loop
-fsched-interblock -fsched-spec -fschedule-insns
-fschedule-insns2 -fstrict-aliasing -fstrict-overflow -ftree-pre
-ftree-vrp
cpu架构的优化选项,通常是-mcpu(将被取消);-march,-mtune
Debug选项:
在gcc编译源代码时指定-g选项可以产生带有调试信息的目标代码,gcc可以为多个不同平台上帝不同调试器提供调试信息,默认gcc产生的调试信息是为gdb使用的,可以使用-gformat 指定要生成的调试信息的格式以提供给其他平台的其他调试器使用.常用的格式有
-ggdb:生成gdb专用的调试信息,使用最适合的格式(DWARF 2,stabs等)会有一些gdb专用的扩展,可能造成其他调试器无法运行.
-gstabs:使用stabs格式,不包含gdb扩展,stabs常用于BSD系统的DBX调试器.
-gcoff:产生COFF格式的调试信息,常用于System V下的SDB调试器;
-gxcoff:产生XCOFF格式的调试信息,用于IBM的RS/6000下的DBX调试器;
-gdwarf-2:产生DWARF version2 的格式的调试信息,常用于IRIXX6上的DBX调试器.GCC会使用DWARF version3的一些特性.
可以指定调试信息的等级:在指定的调试格式后面加上等级:
如: -ggdb2 等,0代表不产生调试信息.在使用-gdwarf-2时因为最早的格式为-gdwarf2会造成混乱,所以要额外使用一个-glevel来指定调试信息的等级,其他格式选项也可以另外指定等级.
gcc可以使用-p选项指定生成信息以供porf使用
5 、GCC常用选项
选项 | 含义 |
---|---|
--help --target-help | 显示 gcc 帮助说明。‘target-help’是显示目标机器特定的命令行选项。 |
--version | 显示 gcc 版本号和版权信息 。 |
-o outfile | 输出到指定的文件。 |
-x language | 指明使用的编程语言。允许的语言包括:c c++ assembler none 。 ‘none’意味着恢复默认行为,即根据文件的扩展名猜测源文件的语言。 |
-v | 打印较多信息,显示编译器调用的程序。 |
-### | 与 -v 类似,但选项被引号括住,并且不执行命令。 |
-E | 仅作预处理,不进行编译、汇编和链接。如上图所示。 |
-S | 仅编译到汇编语言,不进行汇编和链接。如上图所示。 |
-c | 编译、汇编到目标代码,不进行链接。如上图所示。 |
-pipe | 使用管道代替临时文件。 |
-combine | 将多个源文件一次性传递给汇编器。 |
6、 其他GCC选项
更多有用的GCC选项:
命令 | 描述 |
---|---|
-l library -llibrary | 进行链接时搜索名为library的库。 例子: $ gcc test.c -lm -o test |
-Idir | 把dir加入到搜索头文件的路径列表中。 例子: $ gcc test.c -I../inc -o test |
-Ldir | 把dir加入到搜索库文件的路径列表中。 例子: $ gcc -I/home/foo -L/home/foo -ltest test.c -o test |
-Dname | 预定义一个名为name的宏,值为1。 例子: $ gcc -DTEST_CONFIG test.c -o test |
-Dname=definition | 预定义名为name,值为definition的宏。 |
-ggdb -ggdblevel | 为调试器 gdb 生成调试信息。level可以为1,2,3,默认值为2。 |
-g -glevel | 生成操作系统本地格式的调试信息。-g 和 -ggdb 并不太相同, -g 会生成 gdb 之外的信息。level取值同上。 |
-s | 去除可执行文件中的符号表和重定位信息。用于减小可执行文件的大小。 |
-M | 告诉预处理器输出一个适合make的规则,用于描述各目标文件的依赖关系。对于每个 源文件,预处理器输出 一个make规则,该规则的目标项(target)是源文件对应的目标文件名,依赖项(dependency)是源文件中 `#include引用的所有文件。生成的规则可 以是单行,但如果太长,就用`\'-换行符续成多行。规则 显示在标准输出,不产生预处理过的C程序。 |
-C | 告诉预处理器不要丢弃注释。配合`-E'选项使用。 |
-P | 告诉预处理器不要产生`#line'命令。配合`-E'选项使用。 |
-static | 在支持动态链接的系统上,阻止连接共享库。该选项在其它系统上 无效。 |
-nostdlib | 不连接系统标准启动文件和标准库文件,只把指定的文件传递给连接器。 |
Warnings | |
-Wall | 会打开一些很有用的警告选项,建议编译时加此选项。 |
-W -Wextra | 打印一些额外的警告信息。 |
-w | 禁止显示所有警告信息。 |
-Wshadow | 当一个局部变量遮盖住了另一个局部变量,或者全局变量时,给出警告。很有用的选项,建议打开。 -Wall 并不会打开此项。 |
-Wpointer-arith | 对函数指针或者void *类型的指针进行算术操作时给出警告。也很有用。 -Wall 并不会打开此项。 |
-Wcast-qual | 当强制转化丢掉了类型修饰符时给出警告。 -Wall 并不会打开此项。 |
-Waggregate-return | 如果定义或调用了返回结构体或联合体的函数,编译器就发出警告。 |
-Winline | 无论是声明为 inline 或者是指定了-finline-functions 选项,如果某函数不能内联,编译器都将发出警告。如果你的代码含有很多 inline 函数的话,这是很有用的选项。 |
-Werror | 把警告当作错误。出现任何警告就放弃编译。 |
-Wunreachable-code | 如果编译器探测到永远不会执行到的代码,就给出警告。也是比较有用的选项。 |
-Wcast-align | 一旦某个指针类型强制转换导致目标所需的地址对齐增加时,编译器就发出警告。 |
-Wundef | 当一个没有定义的符号出现在 #if 中时,给出警告。 |
-Wredundant-decls | 如果在同一个可见域内某定义多次声明,编译器就发出警告,即使这些重复声明有效并且毫无差别。 |
Optimization | |
-O0 | 禁止编译器进行优化。默认为此项。 |
-O -O1 | 尝试优化编译时间和可执行文件大小。 |
-O2 | 更多的优化,会尝试几乎全部的优化功能,但不会进行“空间换时间”的优化方法。 |
-O3 | 在 -O2 的基础上再打开一些优化选项:-finline-functions, -funswitch-loops 和 -fgcse-after-reload 。 |
-Os | 对生成文件大小进行优化。它会打开 -O2 开的全部选项,除了会那些增加文件大小的。 |
-finline-functions | 把所有简单的函数内联进调用者。编译器会探索式地决定哪些函数足够简单,值得做这种内联。 |
-fstrict-aliasing | 施加最强的别名规则(aliasing rules)。 |
Standard | |
-ansi | 支持符合ANSI标准的C程序。这样就会关闭GNU C中某些不兼容ANSI C的特性。 |
-std=c89 -iso9899:1990 | 指明使用标准 ISO C90 作为标准来编译程序。 |
-std=c99 -std=iso9899:1999 | 指明使用标准 ISO C99 作为标准来编译程序。 |
-std=c++98 | 指明使用标准 C++98 作为标准来编译程序。 |
-std=gnu9x -std=gnu99 | 使用 ISO C99 再加上 GNU 的一些扩展。 |
-fno-asm | 不把asm, inline或typeof当作关键字,因此这些词可以用做标识符。用 __asm__, __inline__和__typeof__能够替代它们。 `-ansi' 隐含声明了`-fno-asm'。 |
-fgnu89-inline | 告诉编译器在 C99 模式下看到 inline 函数时使用传统的 GNU 句法。 |
C options | |
-fsigned-char -funsigned-char | 把char定义为有/无符号类型,如同signed char/unsigned char。 |
-traditional | 尝试支持传统C编译器的某些方面。详见GNU C手册。 |
-fno-builtin -fno-builtin-function | 不接受没有 __builtin_ 前缀的函数作为内建函数。 |
-trigraphs | 支持ANSI C的三联符( trigraphs)。`-ansi'选项隐含声明了此选项。 |
-fsigned-bitfields -funsigned-bitfields | 如果没有明确声明`signed'或`unsigned'修饰符,这些选项用来定义有符号位域或无符号位域。缺省情况下,位域是有符号的,因为它们继承的基本整数类型,如int,是有符号数。 |
-Wstrict-prototypes | 如果函数的声明或定义没有指出参数类型,编译器就发出警告。很有用的警告。 |
-Wmissing-prototypes | 如果没有预先声明就定义了全局函数,编译器就发出警告。即使函数定义自身提供了函数原形也会产生这个警告。这个选项 的目的是检查没有在头文件中声明的全局函数。 |
-Wnested-externs | 如果某extern声明出现在函数内部,编译器就发出警告。 |
C++ options | |
-ffor-scope | 从头开始执行程序,也允许进行重定向。 |
-fno-rtti | 关闭对 dynamic_cast 和 typeid 的支持。如果你不需要这些功能,关闭它会节省一些空间。 |
-Wctor-dtor-privacy | 当一个类没有用时给出警告。因为构造函数和析构函数会被当作私有的。 |
-Wnon-virtual-dtor | 当一个类有多态性,而又没有虚析构函数时,发出警告。-Wall会开启这个选项。 |
-Wreorder | 如果代码中的成员变量的初始化顺序和它们实际执行时初始化顺序不一致,给出警告。 |
-Wno-deprecated | 使用过时的特性时不要给出警告。 |
-Woverloaded-virtual | 如果函数的声明隐藏住了基类的虚函数,就给出警告。 |
Machine Dependent Options (Intel) | |
-mtune=cpu-type | 为指定类型的 CPU 生成代码。cpu-type可以是:i386,i486,i586,pentium,i686,pentium4 等等。 |
-msse -msse2 -mmmx -mno-sse -mno-sse2 -mno-mmx | 使用或者不使用MMX,SSE,SSE2指令。 |
-m32 -m64 | 生成32位/64位机器上的代码。 |
-mpush-args -mno-push-args | (不)使用 push 指令来进行存储参数。默认是使用。 |
-mregparm=num | 当传递整数参数时,控制所使用寄存器的个数。
|
6.1 选项 -fPIC
PIC是Position-Independent Code的缩写。在计算机系统中,PIC和PIE(Position-Independent Executable)是可以在主存中不同位置执行的目标代码。PIC经常被用在共享库中,这样就能将相同的库代码为每个程序映射到一个位置,不用担心覆盖掉其他程序或共享库。
要想实现位置无关,代码必须通过特定的方式编写、编译才行。比如对于固定地址的绝对跳转指令,就需要使用相对应的相对跳转指令代替,相对位置的计算通过对指令计数器的计算得到。在某些特定的体系结构上(如AMD64),共享库就必须支持PIC。
另外位置独立还需要与重分配(reallocate)区分,后者是指在计算机中将符号因引用或者库的名字用主存中的可用地址代替。虽然能在运行时通过装载器(loader)完成,但通常是在编译阶段由连接器(linker)完成。编译器或者汇编器通常会生成从0位置开始的可执行代码,在代码运行之前,这些相对地址将修改为正确的运行时地址。
通常congfigure能够检测出编译器是否支持-fPIC,是通过编译一个小程序,并且检查stderr输出实现的。若此时编译器输出了任何的警告,就视为不支持。此时如果用户在CFLAGS或者CXXFLAGS中指定了一个错误的flag,那编译任何程序都会出警告,于是就会被判为-fPIC不可用。
现在我们就可以考虑以下三种情况需要-fPIC
-
不需要动态链接库的可执行程序:
因为通常可执行程序会被装载到固定的地址,并从此处开始运行,所以普通可执行程序不需要-fPIC -
静态链接库:相当于一个大.o文件结合,又被称作可充分配对象。它们包含了一些可将它们在内存中改变位置的信息(使用重分配移动),所以静态链接库也不需要-fPIC
-
动态链接库:因为动态链接库就是为了实现位置无关,所以需要使用-fPIC.
7 、linux 添加动态连接库的方法:
Linux共享库路径配置
Linux下找不到共享库文件的典型现象为明明已经安装某个软包(如libnet,mysql),编译连接可以正常进行,但是在运行时出现如“error while loading shared libraries: libnet.so.1:cannot open shared object file :No such file or directory”的错误提示。
原因是Linux下的共享库路径配置不正确。默认的linux共享库搜索路径为/lib和/usr/lib两个目录(不包含子目录),若共享库不在这两个路径,不能自动连接到(最典型的就是/usr/local/lib)。
解决方法有五种:
1,连接时使用静态库(.a文件)。在g++命令上写入该.a文件的完整路径。
2,使用LD_LIBRARY_PATH环境变量。(临时有效,且有时候没有效果),把需要添加的路径加入到LD_LIBRARY_PATH中,注意如果多于一个要用冒号隔开。如:export LD_LIBRARY_PATH=/usr/local/lib/minigui
3,将动态库文件复制到可以搜索到的路径(一般是系统默认的路径,如/lib、/usr/lib)里面,这样比较狠,但是可能导致一些后即问题。
4,编译的时候设定:在编译源码的时候可以用参数:-Wl, -rpath指定动态搜索的路径即可。
5,修改系统文件/etc/ld.so.conf,添加路径,运行ldconfig命令。
第五种方法的说明如下:
在/etc/ld.so.conf文件中指定了默认的动态链接库查找路径,我的/etc/ld.so.conf文件内容是这样的include /etc/ld.so.conf.d/*.conf。也就是说它间接的指定了定义路径的文件,我们只需要把需要的路径加到/etc/ld.so.conf.d目录下的任何一个文件中,再运行ldconfig就可以了,但为了容易理解,最好是找一个相关的文件,或者重新建立一个文件,把需要添加的路径写入然后运行ldconfig
ldconfig几个需要注意的地方
1. 往/lib和/usr/lib里面加东西,是不用修改/etc/ld.so.conf的,但是完了之后要调一下ldconfig,不然这个library会找不到
2. 想往上面两个目录以外加东西的时候,一定要修改/etc/ld.so.conf,然后再调用ldconfig,不然也会找不到