前面我们对GCC的一些常用用法作了简单的介绍,包括如何编译、如何连接、如何查错及如何设置搜索路径,有了这些基本知道,开发一个C语言程序并进行编译执行就没有问题了。那么接下来我们了解了一下GCC附带一些工具,首先就讲一个ar工具,我们前面说了.o对象文件在系统中通常是压缩之后放在指定目录下。这些.o文件通常用来处理一类相关问题。那么ar工具就是将这些对象文件打包成一个压缩库文件。注意通常是静态库文件。动态库文件产生方式比较复杂,因为这些.o文件不像静态库用gcc �Cc 来生成它有一个其它过程。这里首先讲述静态库的创建,静态库通常是以.a结尾,存储在/usr/lib目录下,命名以lib开始,如libmath.a。使用ar命名可以将多个.o文件生成.a文件。ar命令格式 ar [-]{dmpqrtx}[abcfilndpssuvv] [member name][count] archive files ……. 其中{dmpqrtx}部分称为操作选项,【abcfilnopssuvv】部分称为任选项。其中d 表示从库中删除模块,m可以在库中移动成员顺序,优先使用相同的符号定义。p显示库指定成员到标准输出,r在库中插入模块,当插入模块已在库时替换。 t显示模块清单。a表示在库中已存在的成员后面添加一个新文件,b同a功能相似,只是在这个成员之前, c表示新建一个库,不论这个库是否存在。
创建一个静态压缩库语法是ar cr libName.a file.o file1.o….,查看一个静态库含有的.o用这个命令 ar t libName.a。
动态库首先对.o文件生成需要是一个无位置无关的目录文件,使用gcc -fPIC命名。
前面讲了几种与头文件和库的搜索路径关系,我们实践一下,如下图所示:
前面我们学习了静态库,静态库是.a结尾,静态库与主程序进行连接时,是将机器码拷到主程序的可执行文件中。还有一种以.so结尾的是动态库。动态库通常不拷贝进最终可执行文件中,进入到可执行文件中是一些小的函数表。这样做法一方面可以使可执行文件字节小。另一方面也便于共享内存时节约内存。并且当库实现修改时,还可以不需要重新编译主程序。因为这些优点,GCC默认使用动态库。默认的搜索路径也是/usr/local/lib和/usr/lib,全局环境变量是LD_LIBRARY_PATH.
库文件讲述到此,就下来对一些常见的GCC选项作一个简单了解。我们知道C语言和C++语言都不是由哪一家公司来控制,它是由一个组织来控制。但是因为C的草根性,其标准和实现都是有不同版本。那么GCC到底是哪一个版本的C呢?缺省情况下是GNU C,这是标准ANSI/ISO上的扩充。那么如果我要用标准ANSI C呢,可以使用选项-ansi和-pedantic。另外标准也是有好多种,那怎么选择呢?可以使用-std来指定特定标准,如-std=c89 -std=iso9899:1900 -std=c99 �Cstd=iso9899:199409 -std=iso9899:1999等等。这些差异它不是GCC带来的,它是由C带来的。因此GCC也支持这些差异化。这些区别只能通过工作中进行积累,实际上也就是一些常见特定宏的不同和一些关键字的不同如下:
GCC编译时用于辅助查错的选项就-Wall,这个选项是告警选项,通常编译时时候除了一特定告警之外,其它的告警也要进行消除,使用这个选项可以方便查找。-Wall下面还分如下几种:
【1】-Wcomment 是用来消除嵌套注释。最好的大段注释方式,是#if 0-----#end if
【2】-Wformat 用来消除printf /scanf中格式化的不正确使用
【3】-Wunused 用来消除未使用的已定义变量
【4】-Wimplicit 用来消除函数未声明
【5】-Wreturn-type 用来消除未函数没有返回值。
实际上不需要区分时直接使用-Wall,但一些其它选项如-W �CWconversion �CWshadow �CWcast-qual �CWwrite-strings -Wtrditional等不含在-Wall中
GCC还有另外一种选项用来控制预处理,这就是宏定义。GCC选项中可以设置宏定义,同在C程序中用define设置是一样的效果,格式是-DNAME=VALUE,未指定VALUE就是缺省1.
通过-DNAME=VALUE可以用控制输入参数,这些输入参数是在编译时替换的,不是运行时变量,一定要注意。另外还有两个辅助选项一个是cpp �CdM /dev/null查看常用宏,gcc �CE test.c用来查看源程序中的宏。
[windriver@windriver-machine ltest]$ cat dm.c
#include <stdio.h>
int main(void)
{
printf("Value of Num if %d\n",NUM);
return 0;
}
[windriver@windriver-machine ltest]$ gcc -Wall dm.c
dm.c: In function \u2018main\u2019:
dm.c:5: error: \u2018NUM\u2019 undeclared (first use in this function)
dm.c:5: error: (Each undeclared identifier is reported only once
dm.c:5: error: for each function it appears in.)
[windriver@windriver-machine ltest]$ gcc -Wall -NUM="23+45" dm.c
gcc: unrecognized option '-NUM=23+45'
dm.c: In function \u2018main\u2019:
dm.c:5: error: \u2018NUM\u2019 undeclared (first use in this function)
dm.c:5: error: (Each undeclared identifier is reported only once
dm.c:5: error: for each function it appears in.)
[windriver@windriver-machine ltest]$ gcc -Wall -DNUM="23+45" dm.c
[windriver@windriver-machine ltest]$ ./a.out
Value of Num if 68
[windriver@windriver-machine ltest]$ gcc -Wall -DNUM=43 dm.c
[windriver@windriver-machine ltest]$ ./a.out
Value of Num if 43
[windriver@windriver-machine ltest]$ gcc -Wall dm.c
dm.c: In function \u2018main\u2019:
dm.c:5: error: \u2018NUM\u2019 undeclared (first use in this function)
dm.c:5: error: (Each undeclared identifier is reported only once
dm.c:5: error: for each function it appears in.)
[windriver@windriver-machine ltest]$ cpp -dM /dev/null|head
#define __DBL_MIN_EXP__ (-1021)
#define __FLT_MIN__ 1.17549435e-38F
#define __CHAR_BIT__ 8
#define __WCHAR_MAX__ 2147483647
#define __DBL_DENORM_MIN__ 4.9406564584124654e-324
#define __FLT_EVAL_METHOD__ 2
#define __DBL_MIN_10_EXP__ (-307)
#define __FINITE_MATH_ONLY__ 0
#define __GNUC_PATCHLEVEL__ 2
#define __SHRT_MAX__ 32767
[windriver@windriver-machine ltest]$
GCC第三个比较重要的选项是-g用来DEBUG的。使用-g编译的可执行文件含有源代码片段和行号。通过使用-g选项来存储附加DEBUG信息,再使用GDB工具就可以查找当时内存与对应源码变量。
GCC第四个选项是用来控制优化级别的。GCC是一个高度优化的编译器,大型软件用它来编译代码,通常需要半天到一天的时间,这个时间消耗如此之大,必须进行优化,优化常见有源码级别,主要包括Common subexpression elimination(CSE)(用于对一些中间表达式设置一个临时变量,避免重复计算),Funcitonn Inlining(FL)(用于消除一些简单函数的创建开销)Loop Unrolling(用于大量循环的简化,比喻步长加大),较低级别上的优化就是调度。 GCC提供了一个选项用来控制优化级别,-OLEVEL其中LEVEL从0到3.
[windriver@windriver-machine ltest]$ gcc -Wall -O0 level.c -o l1
[windriver@windriver-machine ltest]$ gcc -Wall -O1 level.c -o l2
[windriver@windriver-machine ltest]$ gcc -Wall -O2 level.c -o l3
[windriver@windriver-machine ltest]$ gcc -Wall -O3 level.c -o l4
[windriver@windriver-machine ltest]$ time ./l1
sum = 4e+43
real 0m22.396s
user 0m22.383s
sys 0m0.001s
[windriver@windriver-machine ltest]$ time ./l2
sum = 4e+43
real 0m13.982s
user 0m13.964s
sys 0m0.002s
[windriver@windriver-machine ltest]$ time ./l3
sum = 4e+43
real 0m14.175s
user 0m14.160s
sys 0m0.003s
[windriver@windriver-machine ltest]$ time ./l4
sum = 4e+43
real 0m12.492s
user 0m12.479s
sys 0m0.001s
[windriver@windriver-machine ltest]$ gcc - Wall -O3 -funroll-loops level.c -o l5
gcc: Wall: No such file or directory
gcc: -E or -x required when input is from standard input
[windriver@windriver-machine ltest]$ gcc -Wall -O3 -funroll-loops level.c -o l5
[windriver@windriver-machine ltest]$ time ./l5
sum = 4e+43
real 0m10.288s
user 0m10.259s
sys 0m0.002s
[windriver@windriver-machine ltest]$ cat -n level.c
1 #include <stdio.h>
2
3 double powern(double d,unsigned n)
4 {
5 double x = 1.0;
6 unsigned j;
7 for(j=1;j<=n;j++)
8 x*=d;
9 return x;
10 }
11
12 int main(void)
13 {
14 double sum =0.0;
15 unsigned i;
16 for(i=1;i<1000000000;i++)
17 {
18 sum+=powern(i,i%5);
19 }
20 printf("sum = %g\n",sum);
21 return 0;
22 }
23
24
[windriver@windriver-machine ltest]$ vi uninit.c
[windriver@windriver-machine ltest]$ gcc -Wall -c uninit.c
[windriver@windriver-machine ltest]$ gcc -Wall -O2 -c uninit.c
uninit.c: In function \u2018sign\u2019:
uninit.c:3: warning: \u2018s\u2019 may be used uninitialized in this function
[windriver@windriver-machine ltest]$ gcc -Wall -g -c uninit.c
[windriver@windriver-machine ltest]$ cat -n uninit.c
1 int sign(int x)
2 {
3 int s;
4 if(x>0)
5 s=1;
6 else if(x<0)
7 s=-1;
8 return s;
9 }
[windriver@windriver-machine ltest]$
前面所说的GCC的常用选项及命令格式,适用于G++,因为我们前面知道GCC不光是C编译器,也是C++编译器。只是使用g++不用gcc而已,其它都相同。默认情况下cc文件是C++源文件。
最后还有一个重要的功课没说就是编译器是怎样将一个源码最后生成一个可执行文件的呢?编译的过程是怎样呢?我们知道编译是一个多步过程,每一步过程包含了多种工具。这些在编译过程中工具有一个统一名称toolchain。 常见的编译过程分成如下四步:
【1】预处理 GCC调用cpp处理源文件,主要是宏替换和头文件
【2】编译: GCC将前段产生的源码生成汇编,使用-S
【3】汇编,GCC使用AS工具将汇编源程序编译成目标文件
【4】连接。GCC使用LD工具,结合系统库生成可执行文件。
以上整个过程一方面可以通过-v选项进行查看,也可以通过-save-temps选项进行中间文件保存方式来。
GCC生成的可执行文件同WINDOW CL生成的可执行文件是不一样的格式,这就涉及到不同的可执行文件格式标准问题。在LINUX下可能file命令查看出文件格式。
如上图所示,ELF表示(Execuatable and Linking Format),同用于DOS上COFF(Common Object File Format)和用WIN32上PE一样,它是可执行文件的内部格式。 LSB表示(Least signigicant byte)同MSB(most significant byte)用于表示不同平台的edian格式。LSB是大头在后。 not stripped 表示可执行文件含有符号表。
可执行程序通常只包括机器码和符号表,能够查找出符号表的信息对查看一个可执行文件有非常大的参与意义。通常可以使用nm 工具访问。另一个因为可执行程序执行时需要使用动态库,可使用ldd工具来查找所有动态库及路径。
其它还有一个利器就是objdump,这个也可以说是用来导出汇编代码的工具。最后还有两个工具顺便提一下,gprof和gcov。其中使用两个选项一定要注意
[windriver@windriver-machine ltest]$ cat -n collatz.c
1 #include <stdio.h>
2
3 unsigned int step(unsigned int x)
4 {
5 if(x%2 == 0)
6 {
7 return (x/2);
8 }
9 else
10 {
11 return (3*x +1);
12 }
13 }
14
15 unsigned int nseq(unsigned int x0)
16 {
17 unsigned int i =1,x;
18 if(x0 ==1 || x0 ==0)
19 return i;
20 x = step(x0);
21
22 while(x !=1 && x !=0)
23 {
24 x=step(x);
25 i++;
26 }
27 return i;
28 }
29
30 int main(void)
31 {
32 unsigned int i, m =0, im = 0;
33 for(i=1;i<500000;i++)
34 {
35 unsigned int k = nseq(i);
36 if(k>m)
37 {
38 m= k;
39 im =i;
40 printf("sequence length =%u for %u\n",m,im);
41 }
42 }
43 return 0;
44 }
[windriver@windriver-machine ltest]$ gcc -Wall -pg collatz.c
[windriver@windriver-machine ltest]$ ll gmon.out
-rw-rw-r-- 1 windriver windriver 512 2011-07-28 01:06 gmon.out
[windriver@windriver-machine ltest]$ rm -f gmon.out
[windriver@windriver-machine ltest]$ ./a.out
sequence length =1 for 1
sequence length =7 for 3
sequence length =8 for 6
sequence length =16 for 7
sequence length =19 for 9
sequence length =20 for 18
sequence length =23 for 25
sequence length =111 for 27
sequence length =112 for 54
sequence length =115 for 73
sequence length =118 for 97
sequence length =121 for 129
sequence length =124 for 171
sequence length =127 for 231
sequence length =130 for 313
sequence length =143 for 327
sequence length =144 for 649
sequence length =170 for 703
sequence length =178 for 871
sequence length =181 for 1161
sequence length =182 for 2223
sequence length =208 for 2463
sequence length =216 for 2919
sequence length =237 for 3711
sequence length =261 for 6171
sequence length =267 for 10971
sequence length =275 for 13255
sequence length =278 for 17647
sequence length =281 for 23529
sequence length =307 for 26623
sequence length =310 for 34239
sequence length =323 for 35655
sequence length =339 for 52527
sequence length =350 for 77031
sequence length =353 for 106239
sequence length =374 for 142587
sequence length =382 for 156159
sequence length =385 for 216367
sequence length =442 for 230631
sequence length =448 for 410011
[windriver@windriver-machine ltest]$ ll gmon.out
-rw-rw-r-- 1 windriver windriver 512 2011-07-28 01:09 gmon.out
[windriver@windriver-machine ltest]$ gprof a.out |head -10
Flat profile:
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls ns/call ns/call name
63.82 0.24 0.24 62135400 3.80 3.80 step
29.87 0.35 0.11 499999 221.05 693.30 nseq
6.79 0.37 0.03 frame_dummy
% the percentage of the total running time of the
[windriver@windriver-machine ltest]$
[windriver@windriver-machine ltest]$ cat cov.c
#include <stdio.h>
int main(void)
{
int i;
for(i=1;i<10;i++)
{
if(i%3 == 0)
printf("%d is divisible by 3\n",i);
if(i%11 ==0)
printf("d is divisible by 11\n",i);
}
return 0;
}
[windriver@windriver-machine ltest]$ gcc -Wall -fprofie-arcs -ftest-coverage cov.c
cc1: error: unrecognized command line option "-fprofie-arcs"
[windriver@windriver-machine ltest]$ gcc -Wall -fprofile-arcs -ftest-coverage cov.c
cov.c: In function \u2018main\u2019:
cov.c:12: warning: too many arguments for format
[windriver@windriver-machine ltest]$ vi cov.c
[windriver@windriver-machine ltest]$ gcc -Wall -fprofile-arcs -ftest-coverage cov.c
[windriver@windriver-machine ltest]$ ll cov*
-rw-rw-r-- 1 windriver windriver 227 2011-07-28 01:14 cov.c
-rw-rw-r-- 1 windriver windriver 608 2011-07-28 01:14 cov.gcno
[windriver@windriver-machine ltest]$ ./a.out
3 is divisible by 3
6 is divisible by 3
9 is divisible by 3
[windriver@windriver-machine ltest]$ ll cov.*
-rw-rw-r-- 1 windriver windriver 227 2011-07-28 01:14 cov.c
-rw-rw-r-- 1 windriver windriver 176 2011-07-28 01:14 cov.gcda
-rw-rw-r-- 1 windriver windriver 608 2011-07-28 01:14 cov.gcno
[windriver@windriver-machine ltest]$ gcov cov.c
File 'cov.c'
Lines executed:85.71% of 7
cov.c:creating 'cov.c.gcov'
[windriver@windriver-machine ltest]$ ll cov.*
-rw-rw-r-- 1 windriver windriver 227 2011-07-28 01:14 cov.c
-rw-rw-r-- 1 windriver windriver 607 2011-07-28 01:15 cov.c.gcov
-rw-rw-r-- 1 windriver windriver 176 2011-07-28 01:14 cov.gcda
-rw-rw-r-- 1 windriver windriver 608 2011-07-28 01:14 cov.gcno
[windriver@windriver-machine ltest]$ grep "#####" *.gcov
#####: 12: printf("%d is divisible by 11\n",i);
[windriver@windriver-machine ltest]$