GCC编译起步
目录
CSDN: http://blog.csdn.net/cppgp/article/details/3457718
1 C编程中的文件后缀名介绍
- .a 静态库(打包文件)
- .c 未经过预处理的C源码
- .h C头文件
- .i 经过预处理的C源码
- .o 编译之后产生的目标文件
- .s 生成的汇编语言代码
- .so 动态库(动态链接库)
解释:*.a是我们在编译过后用ar打包生成的静态库;*.c一般是我们自己编辑的代码,是我们劳动的结晶;*.h一般是我们手工生成的接口文件,如果愿意,也可在*.c完成后用 GCC的选项-aux-info帮我们生成;*.i是经过预处理后的源码,是由GCC在选项-E编译下自动生成的文件;*.o是编译后产生的目标文件;*.s是GCC在选项-S编译下生成的汇编语言代码,对于性能要求很高的程序可以先生成汇编语言文件并对汇编做优化,然后用优化后的汇编生成目标文件并链接;*.so是动态库,通过GCC的-fpic -shared选项生成。
2 hello.c
/* * hello.c */ #include <stdio.h> int main() { printf("hello, world!/n"); return 0; }
2.1 executable output
$ gcc -o hello hello.c $ ./hello hello, world! 如下编译方式结果相同: $ gcc hello.c -o hello $ ./hello hello, world! 如下编译方式有别于以上编译方案(具体查找ELF和a.out文件格式差别的网络资料,对于此处结果是无任何区别的): $ gcc hello.c $ ./a.out hello, world!
2.2 预处理文件 hello.i
$ gcc -E hello.c -o hello.i $ ls a.out hello hello.c hello.i hello.i 就是新生成的文件 如下语句结果相同: $ gcc -E -o hello.i hello.c 如果不设定输出文件,则打印到标准终端,此时我们可以用 less 查看: $ gcc -E hello.c | less # 1 "hello.c" # 1 "<built-in>" # 1 "<command line>" # 1 "hello.c" # 1 "/usr/include/stdio.h" 1 3 4 # 28 "/usr/include/stdio.h" 3 4 # 1 "/usr/include/features.h" 1 3 4 # 329 "/usr/include/features.h" 3 4 .............................. 或者执行: $ gcc -E hello.c -o hello.i $ vi hello.i 1 # 1 "hello.c" 2 # 1 "<built-in>" 3 # 1 "<command line>" 4 # 1 "hello.c" 5 # 1 "/usr/include/stdio.h" 1 3 4 6 # 28 "/usr/include/stdio.h" 3 4 7 # 1 "/usr/include/features.h" 1 3 4 8 # 329 "/usr/include/features.h" 3 4 .......... <中间部分略> .................. 929 # 844 "/usr/include/stdio.h" 3 4 930 931 # 2 "hello.c" 2 932 933 int main() 934 { 935 printf("hello, world!/n"); 936 937 return 0; 938 }
2.3 asm output
$ gcc -S hello.c -o hello.s $ ls a.out hello hello.c hello.i hello.s hello.s就是新生成的文件 如下语句结果相同: $ gcc -S -o hello.s hello.c 如下语句结果相同: $ gcc -S hello.c 也可以采用前一步骤产生的中间文件生成汇编文件: $ gcc -S hello.i -o hello.s $ gcc -S -o hello.s hello.i $ gcc -S hello.i 生成的汇编部分代码如下: $ vi hello.s 1 .file "hello.c" 2 .section .rodata 3 .LC0: 4 .string "hello, world!" 5 .text 6 .globl main 7 .type main, @function 8 main: 9 leal 4(%esp), %ecx 10 andl $-16, %esp 11 pushl -4(%ecx) 12 pushl %ebp
2.4 objective output
$ gcc -c hello.c -o hello.o $ ls a.out hello hello.c hello.i hello.o hello.s hello.o就是新生成的目标文件: 如下语句结果相同: $ gcc -c -o hello.o hello.c 如下语句结果相同: $ gcc -c hello.c 也可以采用前面步骤产生的中间文件hello.i或hello.s来生成目标文件: $ gcc -c hello.i $ gcc -c hello.s 我们可以用 objdump 查看 hello.o 的二进制码: $ objdump -s hello.o hello.o: file format elf32-i386 Contents of section .text: 0000 8d4c2404 83e4f0ff 71fc5589 e55183ec .L$.....q.U..Q.. 0010 04c70424 00000000 e8fcffff ffb80000 ...$............ 0020 000083c4 04595d8d 61fcc3 .....Y].a.. Contents of section .rodata: 0000 68656c6c 6f2c2077 6f726c64 2100 hello, world!. Contents of section .comment: 0000 00474343 3a202847 4e552920 342e312e .GCC: (GNU) 4.1. 0010 31203230 30373031 30352028 52656420 1 20070105 (Red 0020 48617420 342e312e 312d3532 2900 Hat 4.1.1-52).
2.5 step one by one
$ gcc -o hello hello.i $ ./hello hello, world! $ gcc -o hello hello.s $ ./hello hello, world! $ gcc -o hello hello.o $ ./hello
3 静态库/static library
linux下静态库的生成比较方便。在生成目标文件后用 ar 打包即可。在中大型项目中一个模块一般会做成一个静态库,以方便管理、提高编译、链接效率。
本小节的展示针对 main.c、func1.c、func2.c三个文件。
/* * main.c */ #include <stdio.h> extern int func1(); extern int func2(); int main() { int i; i = func1(); printf("func1 return = %d/n",i); i = func2(); printf("func2 return = %d/n",i); return 0; }
/* * func1.c */ int func1() { return 100; }
/* * func2.c */ int func2() { return 200; }
compile:
$ gcc -c func1.c $ gcc -c func2.c $ ls func1.c func1.o func2.c func2.o main.c func1.o 和 func2.o 是我们生成的目标文件。打包指令如下: $ ar -r libfunc.a func1.o func2.o 我们查看 libfunc.a 中的文件: $ ar -t libfunc.a func1.o func2.o 现在用静态库和 main.c 共同生成目标程序: $ gcc -o main main.c libfunc.a $ ./main func1 return = 100 func2 return = 200 和我们的预期相符合。下面我们进入动态库。
4 静态库/dynamic library
首先我们生成目标文件,但是需要加编译器选项 -fpic 和链接器选项 -shared $ gcc -fpic -c func1.c $ gcc -fpic -c func2.c $ gcc -shared -o libfunc.so func1.o func2.o $ ls func1.c func1.o func2.c func2.o libfunc.so main.c libfunc.so就是我们生成的目标动态库。我们用动态库和 main.c 生成目标程序: $ gcc -o main main.c -L. -lfunc 注意,我们用 -L. -lfunc 作为编译选项。-L. 表从当前目录查找需要的动态库,-lfunc 是动态库的调用规则。Linux系统下的动态库命名方式是 lib*.so,而在链接时表示位 -l* , *是自己起的库名。下面我们运行它: $ ./main ./main: error while loading shared libraries: libfunc.so: cannot open shared object file: No such file or directory 提示一个错误,指示无法找到动态库。在linux下最方便的解决方案是拷贝libfunc.so到绝对目录 /lib 下。但是只有超级用户才有这个权限。另外一个方案是更改环境变量 LD_LIBRARY_PATH。如下: $ $ export LD_LIBRARY_PATH=`pwd` $ ./main func1 return = 100 func2 return = 200 运行成功。现在我们更改动态库的函数而不重新链接。如下: 更改 func1.c 为: int func1() { return 101; } 更改 func2.c 为: int func2() { return 202; } 重新生成库: $ gcc -fpic -shared func1.c func2.c -o libfunc.so $ ./main func1 return = 101 func2 return = 202 可以看出,动态库已经更新了。
5 结束语
本文简单介绍了linux下如何使用gcc进行编译程序、以及简单的静态、动态库的生成。静态库提供了一种打包管理方案,而动态库使程序局部更新成为了可能,更重要的是,当有多份实例存在时,动态库可减小内存的消耗(只占用一份代码空间)。