标签:
在Linux环境下,我们通常用gcc将C代码编译成可执行文件,如下就是一个简单的例子:
#include <stdlib.h>
#include <stdio.h>
void main(void)
{
printf("hello world!\r\n");
}
可以通过如下指令来编译出一个可执行文件:
gcc hello.c
执行完该命令后,就会得到一个a.out的可执行文件。
前面的例子只是简单的介绍了一下gcc的使用方法,熟悉c编程的朋友就会知道,该步骤其实包含了预处理–>编译–>汇编–>链接四步,这四步分别实现的功能如下:
由此可以看出,每一个阶段的输出其实就是下一个阶段的输入,用gcc是可以单独执行这四步的:
gcc -E hello.c -o hello.i
gcc -S hello.i -o hello.s
gcc -c hello.s -o hello.o
gcc hello.o -o hello
实际上,由于这四个步骤太过于复杂,往往可以像我上面那样全部集中到一个命令中来执行:
gcc hello.c -o hello
这里我加了一个-o参数来指定输出名称,而不是默认的a.out。
-o 优化选项, 这个选项不是标准的
-O0指定不优化
gcc hello.c -O3 -O0 hello 当出现多个优化时,以最后一个为准!!
如果有多个文件,则可以通过如下方式全部集中起来。
gcc -o test first.c second.c third.c
这个全生成的方式虽然非常简单,但是存在的一个问题就是:当项目较大时,如果只改了一个文件,仍需要重新编译索引文件。
为了解决这个问题,我们往往把这个编译过程拆分成两步:
将所有.o文件链接成执行文件
gcc –c first.c
gcc –c second.c
gcc –c third.c
gcc -o test first.o second.o third.o
这样,当third.c文件发生改变时,只需要重新编译third.c和链接即可,这样就省去了未变化文件的编译时间,也就是我们通常所说的增量编译。
gcc –c third.c
gcc -o test first.o second.o third.o
从上面的使用方法中我们也可以看到:
由于程序员往往并不关心前面两个几个阶段生成的输出文件,通常我们也把预处理、编译、汇编三个阶段合并在一起,统称为编译,输入.c,生成.o。
前面其实已经演示过-E、–S、–c、–o等几个参数的用法,其中-E及-S很少会用到,-c用于编译生成.o文件,-o用于指定输出文件名称。除了这几个生成控制的参数外,还有许多参数设置,这里主要介绍一下几个常用的:
包含头文件和库:
宏定义:
调试和可执行文件形式:
告警选项:
除了gcc编译器外,还有另外一个编译器g++,很多人往往搞不清楚这两个编译器的区别,很多人望文生义的认为gcc只能编c代码,g++只能编c++代码。实际上这两个编译器的主要区别如下:
有时我们需要将一组代码编成一个库,从而方便其复用。例如,我们调用的STL和系统函数都是以这种方式提供的。另外,当项目工程较大时,为了使其模块化方便分工,有时也需要将其创建自己的链接库。
代码演示:
// stack.c
#include <stdio.h>
char stack[512];
int top = -1;
char pop(void){
return stack[top--];
}
void push(char c){
stack[++top] = c;
}
要把stack.c编成lib,需要经过如下两个步骤:
gcc -c stack.c
ar cr libstack.a stack.o
创建测试文件
// main.c
#include <stdio.h>
char pop();
void push(char c);
void main(void){
push(‘a‘);
push(‘b‘);
printf("%c\n", pop());
}
链接编译:
gcc -o run main.c -L. -lstack
上述过程中,用到了条之前没见过的命令ar。ar是archive的缩写,也就是归档的意思,平时我们用得更多的是另一条归档命令tar。ar和tar的功能其实比较类似,但ar命令做了一些额外的处理,它会为被归档的目标文件中的符号建立索引,当和应用程序链接时,建立的这些索引将回收链接过程
ar命令的参数比较多,如果只是创建lib库的话,通常只用到了cr这两个组合参数。该命令是可以接受多个输入文件,统一合并到一个库中。
ar cr libtest.a first.o second.o third.o
在通过ar创建lib后,可以通过ar -t命令查看该lib里打包了那些.o文件
ar -t libstack.a
stack.o
此外,还可以通过nm命令来查看符号表等更多信息
nm libstack.a
stack.o:
0000000000000000 T pop
0000000000000021 T push
0000000000000200 C stack
0000000000000000 D top
传统方式下,库函数的链接是在编译器完成的,所有相关对象在编译的时候被整合成一个可执行文件。与此相比,我们也可以把对库函数的链接载入推迟到程序运行的时期,也就是我们所称作的动态链接。
动态链接的优点
除了静态链接库所有的模块化和代码复用外,动态链接库还有如下优点:
创建动态链接库的方式比较简单,还是按静态链接库的例子,我们只需要通过gcc -shared指令即可创建一个libstack.so的动态库(静态库一般以.a作为扩展名,动态库一般以.so作为扩展名)。
gcc -c -fpic stack.c
gcc -shared -o libstack.so stack.o
这里必须带上-fpic,使输出的对象模块是按照可重定位地址方式生成的。
在链接阶段使用动态库的方式基本上和静态库一致。
gcc -o run main.c -L. -lstack
编译玩这个程序后,我们执行后却发现,它报动态链接库找不到的错误提示。
./run
./run: error while loading shared libraries: libstack.so: cannot open shared object file: No such file or directory
我们也可以通过ldd命令查看某程序当前对动态链接库的依赖情况:
ldd run
linux-vdso.so.1 => (0x00007fff366fe000)
libstack.so => not found
libc.so.6 => /lib64/libc.so.6 (0x00002ba53ad1f000)
/lib64/ld-linux-x86-64.so.2 (0x00002ba53ac03000)
ldd的结果表明了我们生成的libstack.so找不到。因为动态链接库是一个可以共享的文件,因此往往存放在一个公共的位置,在Linux系统中程序查找动态链接库的规则如下:
很明显,这几个路径都不包含当前路径。要解决上述问题,一个简单的方式就是把当前路径加到环境变量中:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
然后再次用ldd名称测试,发现现在就能找到我们的链接库了。
ldd run
linux-vdso.so.1 => (0x00007fff581ff000)
libstack.so (0x00002ad148cf0000)
libc.so.6 => /lib64/libc.so.6 (0x00002ad148e0b000)
/lib64/ld-linux-x86-64.so.2 (0x00002ad148bd4000)
不过,很多大牛并不建议通过修改LD_LIBRARY_PATH这种方式
LD_LIBRARY_PATH is not the answer http://prefetch.net/articles/linkers.badldlibrary.html
Why LD_LIBRARY_PATH is bad http://xahlee.org/UnixResource_dir/_/ldpath.html
LD_LIBRARY_PATH - just say no http://blogs.sun.com/rie/date/20040710
标签:
原文地址:http://www.cnblogs.com/linzhenjie/p/5485549.html