1、1,C语言-GCC,上海*通信技术有限公司Jim()2011-05,2,培训大纲,一、gcc简介 二、gcc编译过程 三、gcc编译选项 四、静态/动态链接库 五、其它,3,gcc简介,名称: GNU project C and C+ Compiler GNU Compiler Collection gcc是一个全功能的 ANSI C 兼容编译器,它是所有UNIX系统可用的C编译器。 gcc是可以在多种硬体平台上编译出可执行程序的超级编译器,其执行效率与一般的编译器相比平均效率要高20%30%。,4,查看版本信息,5,查看安装路径,6,相关文件类型,c 为后缀的文件,C语言源代码文件; .a
2、为后缀的文件,是由目标文件构成的档案库文件; .C,.cc或.cxx 为后缀的文件,是C+源代码文件; .h 为后缀的文件,是程序所包含的头文件; .i 为后缀的文件,是已经预处理过的C源代码文件; .ii 为后缀的文件,是已经预处理过的C+源代码文件; .m 为后缀的文件,是Objective-C源代码文件; .o 为后缀的文件,是编译后的目标文件; .s 为后缀的文件,是汇编语言源代码文件; .S 为后缀的文件,是经过预编译的汇编语言源代码文件。,7,gcc编译的4个过程,预处理(也称预编译,Preprocessing): 命令gcc首先调用cpp进行预处理,在预处理过程中,对源代码文件中
3、的文件包含(include)、预编译语句(如宏定义define等)进行分析。编译(Compilation): 接着调用cc1进行编译,这个阶段根据输入文件生成以.o为后缀的目标文件。汇编(Assembly): 汇编过程是针对汇编语言的步骤,调用as进行工作。连接(Linking): 当所有的目标文件都生成之后,gcc就调用 ld来完成最后的关键性工作,这个阶段就是连接,8,可执行程序的生成过程,预处理(Preprocessing):分析各种预处理命令,如#define, #include, #if等; 编译(Compilation): 根据输入文件产生汇编语言的程序; 汇编(Assembly)
4、: 将汇编语言输入,产生扩展名为.o的目标文件; 链接(Linking):以.o目标文件,库文件作为输入,生成可执行文件;,源程序文件 (.h, .c),经预处理的文件 (.i),汇编语言文件 (.s),目标文件 (.o),可执行程序 (.out),9,培训大纲,一、gcc简介 二、gcc编译过程 三、gcc编译选项 四、静态/动态链接库 五、其它,10,GCC编译过程,典型的编译过程test.c 预处理 test.i 编译 test.s 汇编test.o 连接 test$ cat test.c (查看程序源代码)#include int main(int argc, char *argv)
5、printf(“hello worldn“); return 0;$ gcc o test test.c (编译连接程序) $ ./test (执行test程序),11,1、没有任何选项:gcc helloworld.c 结果会在和helloworld.c相同的目录下产生一个a.out的可执行文档。 2、-o选项,指定输出文档名:gcc -o helloworld helloworld.c -o意思是Output即需要指定输出的可执行文档的名称。这里的名称为helloworld。 3、-c选项,只编译汇编 ,不连接:gcc -c helloworld.c -c意思就是Compile,产生一个叫
6、helloworld.o的目标文档 4、-S选项,产生汇编源文档:gcc -S helloworld.c -S意思就是aSsemble,产生一个叫helloworld.s的汇编源文档 5、-E选项,预处理C源文档:gcc -E helloworld.c -E意思就是prEprocess。输出不是送到一个文档而是标准输出。当然能够对他进行重定向: gcc -E helloworld.c helloworld.txt,12,预处理,预编译命令: $ gcc -o test.i -E test.c 或者 $ cpp -o test.i test.c 这里cpp不是值c plus plus,而是the
7、 C Preprocessor 执行结果: 生成预处理后的文件test.i, 该文件包含了 test.c需要的所有的类型和函数申明。原理:读取c源程序,对伪指令和特殊符号进行处理。 包括宏,条件编译,包含的头文件,以及一些特殊符号。 基本上是一个替换的过程。,13,#define用法,#include #define AA 100 int main(void) AABB printf(“hellon”); 预处理命令 gcc E hello.c DBB=hello gcc E hello.c DBB=“printf(”hello”);” gcc E hello.c DBB (等效于-DBB=1
8、),注释这一行看看预处理的结果,-D表示在命令行中传入宏定义 -DBB=后面是一个宏的定义,可以加双引号。,14,#define带参数,#include #define AA(a,b) a = b int main(void) AA(int a, 1);printf(“hellon”); 预处理命令 gcc E hello.c,展开就成了:int a = 1; AA宏带两个参数,15,#define带参数,#include #ifdef AA aa #elif defined BB bb #elif defined CC cc #else other #endif int main(void)
9、 printf(“hellon”); gcc E hello.c DAA=1 aa int main(void) printf(“hellon”); ,#ifdef AA 等效于 #if defined AA 表示当定义了宏AA,表示除此之外的情况,表示否则定义了宏CC的情况,16,#define带参数,#include #if AA aa #elif BB bb #elif CC cc #else other #endif int main(void) printf(“hellon”); ,#if AA 表示AA非零的情况 也就是AA除了0其它数字都为真,表示除此之外的情况,#elif BB
10、 表示BB非零的情况 #elif表示否则如果,17,gcc E hello.c DAA=1,aa int main(void) printf(“hellon”); gcc E hello.c DAA=0 other int main(void) printf(“hellon”); ,18,#的用法,在函数式宏定义中, #运算符用于创建字符串,#运算符后面应该跟一个形参(中间可以有空格或Tab),例如: #define STR(s) # s char *p = STR(helloworld) 结果变成: char *p = “helloworld”,19,#的用法,在宏定义中可以用#运算符把前后
11、两个预处理Token连接成一个预处理Token,和#运算符不同,#运算符不仅限于函数式宏定义,变量式宏定义也可以用。例如:#define FOO(a) foo#a int a = FOO(bar); int b = FOO(); 预处理之后变成:int a = foobar; int b = foo;,20,预处理头文件xxx.h,#ifndef HEADER_FILENAME #define HEADER_FILENAME /* body of header */#endif 当xxx.h被多次包含的时候。,21,有三个头文件和一个C文件,common.h file2.h file3.h m
12、ain.c#ifndef _COMMON_H_ #define _COMMON_H_ static int test(void) printf(“hellon”); #endiffile1.h文件内容如下: #include “common.h”file2.h文件内容如下: #include “common.h”main.c内容如下 #include #include “file1.h” #include “file2.h” int main(void) test(); gcc o main main.c,如果没有写上红色部分的,是什么情况。,22,常用的#debug宏定义,int main(
13、) printf(“here %s %s %dn”, _FILE_, _func_, _LINE_);printf(“here %s %s %dn”, _FILE_, _func_, _LINE_);printf(“here %s %s %dn”, _FILE_, _func_, _LINE_); ,太多printf不好区分 如何打出printf的时候 也打出行号,函数, 文件之类的信息,23,编译,编译命令: $ gcc -o test.s -S test.i (-S编译选项)$ cc1 -o test.s test.i (cc1为C语言真正编译器) 结果:生成汇编文件test.s, tes
14、t.s中包含了AT&T的x86汇编代码。 作用: 通过词法和语法分析,确认所有指令符合语法规则(否则报编译错),之后翻译成对应的中间码,在linux中被称为RTL(Register Transfer Language),通常是平台无关的,这个过程也被称为编译前端。编译后端对RTL树进行裁减,优化,得到在目标机上可执行的汇编代码。gcc采用as作为其汇编器,所以汇编码是AT&T格式的,而不是Intel格式,所以在用gcc编译嵌入式汇编时,也要采用AT&T格式。,24,汇编,汇编命令:$ gcc -o test.o -c test.s$ as -o test.o test.s 执行结果: 生成目标
15、机器指令文件test.o(可以通过 objdump查看汇编指令)原理: 把汇编语言代码翻译成目标机器指令, 用file test.o 可以看到test.o是一个relocatable的ELF文件,通常包含.text .rodata代码段和数据段。可用readelf -r test.o查看需要relocation的部分。,25,链接,执行命令: $ gcc -o test test.o 执行结果: 生成可执行文件test (可用objdump查看) 原理: 将在一个文件中引用的符号同在另外一个文件中该符号的定义链接起来,使得所有的这些目标文件链接成为一个能被操作系统加载到内存的执行体。(如果有链
16、接不到的符号定义,或者重复定义等,会报链接错)。用file test 可以看到test是一个executable的ELF文件。,26,培训大纲,一、gcc简介 二、gcc编译过程 三、gcc编译选项 四、静态/动态链接库 五、其它,27,gcc的使用,基本使用格式 $ gcc 选项 文件名 例:,/text.c #include void main() printf(“Hello World!n”); ,$gcc test.c $./a.out Hello World! 或 $gcc -o test test.c $./test Hello World!,28,常用选项及含义,29,gcc的常
17、用选项,30,gcc的常用选项,31,gcc的常用选项,32,示例,33,-Wall:允许所有有用的警告(建议总是使用该选项),/* bad.c */ #include int main(int argc, char *argv)printf(“Two plus two is %fn”, 4);return 0; ,例1: $ gcc bad.c o bad例2: $ gcc Wall bad.c o bad,33,例1:,34,-o:定义输出文件,例:编译多个源文件 $ gcc -Wall main.c hello.c -o helloworld,/* hello.h */ void hel
18、lo (const char * name);,/* hello.c */ #include #include “hello.h“ void hello (const char * name) printf (“Hello, %s!n“, name); ,/* main.c */ #include “hello.h“ int main (int argc, char *argv) hello (“world“); return 0; ,34,例2:,35,-l:链接外部库文件,库是已经编译好并能被链接入程序的对象文件的集合。库中提供一些最常用的系统函数,比如象C的数学库中求平方根函数sqrt。
19、,库通常被存储在扩展名为“.a”或“.so”的特殊归档文件中。,C标准库自身存放在“/usr/lib/libc.a”中,包含ANSI/ISO C标准指定的各个函数,是默认自动加载的库。,/* sqrt.c */ #include # include int main(int argc, char *argv)double r = sqrt(3.0); printf (“(“The square root of 3.0 is %fn“, r);return 0; ,例: $ gcc -Wall sqrt.c -o sqrt,ccbR6Ojm.o: In function main: ccbR6O
20、jm.o(.text+0x19): undefined reference to sqrt,35,例:,36,-l:链接外部库文件,函数sqrt()并不定义在源程序中或默认的C库“libc.a”中。,为了使得编译器能把sqrt()函数链接到主程序“sqrt.c”,需要提供“libm.a”库。,例: $ gcc -Wall sqrt.c /usr/lib/libm.a -o sqrt,/usr/lib/libm.a,-lm,$ gcc -Wall sqrt.c -lm -o sqrt,编译器选项“-lNAME”试图链接标准库目录下的文件名为“libNAME.a”中的对象文件,36,例:,37,函
21、数库的链接次序,原则:包含函数定义的库应该出现在任何使用到该函数的源文件和对象文件之后,例1: $ gcc -Wall -lm sqrt.c -o sqrt (incorrect) $ gcc -Wall sqrt.c -lm -o sqrt (correct),例2: $ gcc -Wall sqrt.c -lglpk -lm -o sqrt,程序“sqrt.c”用到了GNU Linear Programming库“libglpk.a”,而该库又依次用到数学库“libm.a”,那么应当这么编译:,37,例:,38,-L:设置库文件的搜索路径,如果链接时用到的库不在gcc用到的标准库目录中,就
22、会报这样的错。,/usr/local/lib/ /usr/lib/,例: $ gcc -Wall -L/tmp/lib sqrt.c -lm -o sqrt,-L/tmp/lib,38,例:,39,-I:设置头文件的搜索路径,如果头文件不在gcc用到的标准include文件路径中,就会报这样的错。,/usr/local/include/ /usr/include/,例: $ gcc -Wall -I/tmp/include sqrt.c -lm -o sqrt,-I/tmp/include,39,例:,40,生成预处理文件,命令-E选项使得gcc生成预处理文件后停止。 $gcc E hello
23、.c o hello.i,预处理文件hello.i的部分内容extern void funlockfile (FILE *_stream) ; # 679 “/usr/include/stdio.h“ 3# 2 “hello.c“ 2int main(void) printf(“hello gcc!n“);return 0; ,41,生成汇编文件,命令 $gcc S hello.c o hello.s,汇编文件hello.s的部分内容main:pushl %ebpmovl %esp, %ebpaddl $16, %espmovl $0, %eaxleaveret “,42,生成二进制文件,生成
24、目标文件 命令: $gcc c hello.c o hello.o 生成可执行文件 命令: $gcc hello.c o hello 运行程序 $./hello hello gcc!,43,培训大纲,一、gcc简介 二、gcc编译过程 三、gcc编译选项 四、静态/动态链接库 五、其它,44,静态库与动态库,创建函数库 分类: 静态库: 在编译过程中将库函数代码直接加入到生成的可执行程序中,程序运行过程中不需要利用库函数。 共享库: 编译时,只是在生成的可执行程序中简单指定需要使用的库函数信息,程序运行过程中需要利用库函数。 动态库: 共享库的一种变化形式,目前大都采用共享库的方式。 命名:
25、静态库: 前缀lib+库名+.a (libm.a, libstdc+.a等) 共享库: 前缀lib+库名+.so+.版本号 (libm.so.6, libc.so.6),45,创建静态库,静态函数库是一组目标文件(*.o)的集合 创建步骤:gcc -c test1.c test2.c (生成test1.o, test2.o)ar -cr libtest.a test1.o test2.o (生成函数库libtest.a)ar函数说明: 用途:创建和修改库函数,或从库函数提取目标文件 举例: ar -rs lib-name list-of-files (将列表中的目标文件加入到库中,并产生索引文
26、件) ar -ds lib-name list-of-files (将列表中列出的目标文件从库中删除,并产生索引文件) ar -x lib-name list-of-files (不修改库文件,从库中提取列表中列出的目标文件),46,创建静态库示例源码,创建静态库的方法:ar,caculation.c #include int main(void) int x = 5 ;int y = 3 ;printf (“x + y = %3dn“, add(x, y) );printf (“x - y = %3dn“, minus(x, y) );printf (“x * y = %3dn“, mult
27、iply(x, y) );printf (“x % y = %3dn“, mod(x, y) );return 1; ,add_minus.c int add(int x, int y) int result;result = x + y;return result; int minus(int x, int y) int result;result = x - y;return result; ,multiply_mod.c int multiply(int x, int y) int result;result = x * y;return result; int mod(int x, i
28、nt y) int result;result = x % y;return result; ,47,创建静态库示例操作命令,48,一个容易忽略的顺序问题,静态库不能先于原程序链接,这是因为开始时还没有任何未定义的符号,库中的内容不会被链入,所以应该注意将静态库的使用(-l选项)都写在最后。,49,创建、使用共享库示例,vicknec$ gcc -fPIC -c add_minus.c vicknec$ gcc -fpic -c multiply_mod.c vicknec$ gcc -shared -fpic -o libalg.so add_minus.o multiply_mod.ovi
29、cknec$ gcc -o cacul -lalg caculation.c /usr/bin/ld: cannot find -lalgcollect2: ld returned 1 exit statusvicknec$ gcc -o cacul -L. -lalg caculation.c vicknec$ ./cacul./cacul: error while loading shared libraries: libalg.so: cannot open shared object file: No such file or directory,50,创建共享库步骤,创建共享库gcc
30、 -c -fPIC test1.cgcc -c -fPIC test2.cgcc -shared -fPIC -o libtest.so test1.o test2.o编译使用了共享库的程序gcc -o main Ldir -ltest main.c,51,共享库系统自动动态加载问题,1.拷贝动态库文件到/lib或/usr/lib去$ cp libalg.so /usr/lib or $cp libalg.so /lib2.改变环境变量LD_LIBRARY_PATH$ LD_LIBRARY_PATH=$PWD$ export LD_LIBRARY_PATH,52,应用程序自身完成动态加载,可以
31、在自己的程序中使用 dlopen()。该函数将打开一个新库,并把它装入内存。 dlopen() 在 dlfcn.h 中定义,并在 dl 库中实现。,53,应用程序自身完成动态加载示例,#include #include int main(void) int x = 5 ;int y = 3 ;void * handle;int ( *dl_add )( int, int );int ( *dl_mod )( int, int );,handle = dlopen( “/usr/lib/libalg.so“, RTLD_LAZY );dl_add = dlsym( handle, “add“ )
32、;dl_mod = dlsym( handle, “mod“ );printf (“x + y = %3dn“, dl_add( x, y ) );printf (“x % y = %3dn“, dl_mod( x, y ) );dlclose( handle );return 1; ,54,检查库依赖关系,函数库之间的相互引用 ldd的使用:用于查看库函数之间的依赖性 vicknec gcc-lab$ cd /usr/lib vicknec lib$ ldd libtiff.solibjpeg.so.62 = /usr/lib/libjpeg.so.62 (0x4004c000)libz.s
33、o.1 = /usr/lib/libz.so.1 (0x4006b000)libc.so.6 = /lib/i686/libc.so.6 (0x40079000)/lib/ld-linux.so.2 = /lib/ld-linux.so.2 (0x80000000),55,动态库路径搜索的先后顺序,编译目标代码时指定的动态库搜索路径; 环境变量LD_LIBRARY_PATH指定的动态库搜索路径; 配置文件/etc/ld.so.conf中指定的动态库搜索路径; 默认的动态库搜索路径/lib; 默认的动态库搜索路径/usr/lib.,56,总结:动态共享库的好处,1.动态共享库是共享的,节省了物理
34、开销。 2.库版本更新容易,运行时调用,库更新后不用重新链接。 3.允许用户在运行时再确定调用哪个库。使得在程序中添加或者删除一个模块时,都不需要在编译时指定库。 注意,如果动态共享库无法加载,可能是路径的问题,或是依赖的问题。,57,培训大纲,一、gcc简介 二、gcc编译过程 三、gcc编译选项 四、静态/动态链接库 五、其它,58,GNU C 扩展简介,gcc使用long long数据类型提供64位存储单元。 内联函数:要求内联函数足够短小,使得能够在代码中展开,同时编译器检查函数类型。要使用-O选项。-finline-functions. 使用attribute关键字指明代码相关信息方
35、便优化。 gcc对case做了扩展。case语句可以对应一个范围,在case关键字后列出范围的两个边界值,用“空格省略号空格”分隔。,59,多个文件的C程序,为了方便代码重用,通常将主函数和其他函数放在不同文件中的方法。 每个函数都有函数声明(函数头)和函数实现(函数体)两部分组成。 函数头一般放在头文件中(*.h)中,而函数的定义文件放在实现文件(*.c、*.cpp)中,60,编译多个文件,61,假设3个文件保存在同一个目录下面,如果还使用: 命令: $gcc my_app.c o my_app 会编译通过吗?,62,编译多个文件,目录结构(1) 编译命令$ gcc my_app.c gre
36、eting.c o my_app 目录结构(2) 编译方式(1)$ gcc my_app.c functions/greeting.c o my_app -I functions,63,编译多个文件,目录结构(2) 编译方式(2) 分步编译 命令: 1、$gcc -c my_app.c -I functions 2、$gcc -c functions/greeting.c 3、$gcc my_app.o greeting.o o my_app 思路: 编译每一个.c文件,得到.o的目标文件; 将每一个.o的目标文件链接成一个可执行的文件;,64,ar option,option c Creat
37、e a file option d Delete a file from the archive option p Print a list of files in the archive option q Append files to the archive option r Insert new files in the archive by replacing if a file already exists in the archive option t Displaying contents of an archive option x Extracting files from
38、an archive,65,ranlib Utility,The “ranlib” command is used to create index entries inside an archive file. 示例:$ranlib static-lib-name 等价于 $ar s static-lib-name,66,nm Utility,used to list symbols used in an object file. 示例:$ nm -s libcommon.a $ nm -s a.out 另外:a option with the nm command also shows th
39、e debug symbols.,67,objdump Utility,-f Displaying Header Information -h Displaying Section Headers -d Disassembling a File -a Displaying Information about Library Files,68,size Utility,The size utility displays sizes of each section in an object file. 示例:rootboota# size a.out text data bss dec hex f
40、ilename 1015 232 24 1271 4f7 a.out,69,ldd Utility,The ldd utility is very useful in finding out the dependencies of an executable on shared libraries. 示例:rootboota# ldd a.out libc.so.6 = /lib/i686/libc.so.6 (0x4002c000) /lib/ld-linux.so.2 =/lib/ld-linux.so.2 (0x40000000) rootboota#,70,建立自己的GCC 编译环境,
41、一个系统上如何同时存在并使用多个版本的GCC编译器? 建立目标目录: 在gcc源代码同一级目录下(与源目录$srcdir是同级目录) % mkdir gcc-build % cd gcc-build 配置环境: % /gcc-3.4.0/configure -prefix=/usr/local/gcc-3.4.0 -enable-threads=posix -disable-checking -enable-long-long -host=i386-redhat-linux -with-system-zlib -enable-languages=c,c+,java 将GCC安装在/usr/lo
42、cal/gcc-3.4.0目录下,支持C/C+和JAVA语言,71,编译安装: % make&make install 其它配置: GCC 3.4.0的所有文件,包括命令文件(如gcc、g+)、库文件等都在$destdir目录下分别存放,如命令文件放在bin目录下、库文件在lib下、头文件在include下等。由于命令文件和库文件所在的目录还没有包含在相应的搜索路径内,所以必须要作适当的设置之后编译器才能顺利地找到并使用它们。 用符号连接的方式实现,这样做的好处是我仍然可以使用系统上原来的旧版本的GCC编译器。 % ln -s $destdir/bin/gcc gcc34 库路径的设置:exp
43、ort LD_LIBRARY_PATH=$LD_LIBRARY_PATH:destdir/lib setenv LD_LIBRARY_PATH /usr/local/gcc-3.4.0/lib:$LD_LIBRARY_PATH,72,GCC的编译环境,工具:gcc c编译器,它在链接时使用c库cc1 实际的c编译器cc1plus 实际的c+编译器collect2 使用collect2产生特定的全局初始化代码,后台处理是传递参数给ld完成实际的链接工作。crt0.o 初始化和结束代码libgcc 平台相关的库,73,binutils相关工具: as: gnu汇编工具gprof : 性能分析工具l
44、d : gnu链接器,链接器可以读写各种目标文件中的信息,通过BFD(binary file descriptor)提供的工具实现,BFD定义了类似a.out, elf, coff等目标文件的格式make:objcopy: 目标文件从二进制格式翻译或复制到另一种objdump: 显示目标文件的各种信息strings: 显示文件的字符串strip : 去除符合表readelf : 分析elf并显示信息,74,gcc预定义宏_BASE_FILE_ 完整的源文件名路径_cplusplus 测试c+程序_DATE_ 日期_FILE_ 源文件名_func_ 替代_FUNCTION_,_FUNCTION_
45、以被GNU不推荐使用_TIME_ 日期_LINE_ 行数_VERSION_ gcc版本,75,常用优化选项 gcc默认提供了5级优化选项的集合: -O0:无优化(默认) -O和-O1:使用能减少目标文件大小以及执行时间并且不会使编译时间明显增加的优化.在编译大型程序的时候会显著增加编译时内存的使用. -O2:包含-O1的优化并增加了不需要在目标文件大小和执行速度上进行折衷的优化.编译器不执行循环展开以及函数内联.此选项将增加编译时间和目标文件的执行性能. -Os:专门优化目标文件大小,执行所有的不增加目标文件大小的-O2优化选项.并且执行专门减小目标文件大小的优化选项. -O3:打开所有-O2
46、的优化选项并且增加 -finline-functions, -funswitch-loops,-fpredictive-commoning, -fgcse-after-reload and -ftree-vectorize优化选项.,76,GCC 的编译错误类型 C语法错误 错误资讯文件source.c中第n行有语法错误(syntex errror) 头文件错误 错误资讯找不到头文件head.h(Can not find include file head.h) 档案库错误 错误资讯连接程序找不到所需的函数库 未定义符号 错误资讯有未定义的符号(Undefined symbol)。,77,推荐读物,GCC 中文手册 学习使用GCC Using the GNU Compiler Collection,