在Linux下如何使用GCC编译程序、简单生成 静态库及动态库。 本文适用于Linux下开发初学者。本文初步讲解在Linux下如何使用GCC编译程序、简单生成静态库及动态库。
一、关于安装。一般系统默认是安装好编译器的,并且网络上有大量资料介绍不同发行版本下的安装问题,本文不再描述。
二、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选项生成。
三、hello.c的编译过程
本小节的演示都针对文件 hello.c 进行
-
/*
-
* hello.c
-
*/
-
-
#include <stdio.h>
-
int main()
-
{
-
printf("hello, world!/n");
-
return 0;
-
}
1. 直接生成可执行程序
-
$ 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. 生成预处理后的文件 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 }
-
-
可 见,将近1000行的代码,我们的只占了最末8行。
-
3.生成汇编语言文件 hello.s
-
$ 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
-
// 注释:如果你熟悉,就可以对部分汇编优化以达到更好效果。
4.生成目标文件 hello.o
-
$ 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).
5. 采用中间级文件生成可执行程序
-
$ gcc -o hello hello.i
-
$ ./hello
-
hello, world!
-
-
$ gcc -o hello hello.s
-
$ ./hello
-
hello, world!
-
-
$ gcc -o hello hello.o
-
$ ./hello
-
hello, world!
四、 静态库的生成
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;
-
}
一 下是编译指 令:
-
$ 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
-
-
和 我们的预期相符合。下面我们进入动态库。
-
五、动态库的生成
linux下动态库的生成通过GCC选项实现。案例程序和静态库中的相同。一下是操作指令:
-
首 先我们生成目标文件,但是需要加编译器选项 -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
-
-
可 以看出,动态库已经更新了。
六、结束语
本文简单介绍了linux下如何使用gcc进行编译程序、以及简 单的静态、动态库的生成。静态库提供了一种打包管理方案,而动态库使程序局部更新成为了可能,更重要的是,当有多份实例存在时,动态库可减小内存的消耗 (只占用一份代码空间)。
对本系列知识感兴趣者可继续跟踪阅读后续文章:库的版本管理、GCC的编译选项、Makefile与自动化编译