条件编译,头文件,静态库,共享库与多文件编程
条件编译
条件编译即满足某些条件的时候编译某部分代码,常用于开发多个版本的程序,当满足条件A时,编译出免费版本的软件,当满足条件B时,编译除vip版本的软件,可以提高代码的复用率。条件编译使用"预处理命令+宏定义"来实现,更多宏命令参见
$vi tutu.c
#ifdef VIP //也可以写成#if defined (VIP)
//把免费版改造成VIP版的代码
#elif defined PRO
//把免费版改造成PRO版代码
#endif
//免费版本的代码
$gcc -DVIP tutu.c //将编译出VIP版的软件
头文件header
头文件的编写
C语言的标识符在使用之前一定要声明,把所有的标识符的声明都放在一个头文件中,在预处理阶段把这些声明一股脑的复制到源文件的开头,这样任何标识符在使用之前不就都被声明过了。但这个方案引起了一个问题,就是如果我调用了fcn.c的函数,包括了它的头文件fcn.h,我的同事也这么做,那么编译的文件中不就有了两份fcn.h,而一个项目数百的文件,每个文件都有自己的头文件,还会有相互调用的问题,这样编译器的压力就会很大,所以就有了"头文件卫士":
#ifndef __FCN_H
#define __FCN_H
//fcn.c里标识符声明
#endif //__FCN_N
这三句话就保证了这个头文件在整个程序中只有一份,因为一旦第一次使用这个头文件的时候,__FCN_H还没有被定义,那么他就会被定义,里面的声明代码也会被使用,如果再由文件使用这个头文件,那么由于__FCN_H已经被定义了,条件编译的条件不满足,所以里面的声明代码也就不会再被使用,反正声明有一份就够了。
头文件的使用
头文件需要使用#include宏命令把头文件原封不动的复制到当前文件夹
#include<标准头文件> //在默认头文件路径里查找头文件,如果找不到就报错
#include"头文件路径" //按照指定的路径查找头文件,找不到就到默认头文件路径查找,还是找不到就报错
静态库
静态库:由若干个.o目标文件打包生成的.a文件叫静态库文件, 链接静态库就是将被调用的代码指令到调用模块中,并体现在最终的可执行文件中 ,静态库只能静态链接,
优势
- 不需跳转,执行效率较共享库高一些
- 使用静态库的代码在运行时不需要依赖静态库
劣势
- 静态库占用空间比较大,多次链接(多次复制)之后最终生成的可执行文件比较大
- 修改维护不方便,库文件改一点,链接它的文件就得从头复制一遍
生成静态库
- 编写.c文件 $vi add.c
- 生成.o文件 $cc -c add.c
- 生成静态库文件 $ar –r libadd.a add.o
- 链接静态库文件 $cc -o main main.o -static -ladd -L.
Note:
- 对于临时使用不在公用库目录的库,链接前可以把库的路径添加到LIBRARY_PATH,
$export LIBRARY_PATH=$LIBRARY_PATH:`pwd`
- 或者使用直接链接
$cc main.o ./libadd.a
- 或者使用编译选项链接
$cc main.o -ladd –L.
- 和动态库不同,静态库不存在运行时找不到的问题,编译时就把所有库问题解决了。
- add是库名,libadd不是,是文件名
共享库
共享库就是由若干个目标文件打包生成的xxx.so文件 ,链接共享库不是将被调用代码指令复制到调用模块中,而是将被调用代码指令在共享库中的相对地址复制到调用模块中, 体现在最终的可执行文件中,不论静态链接还是动态链接(共享库可以静态链接也可以动态链接)。 Linux下进行链接的缺省操作是先考虑动态链接库,即如果同时存在静态和共享库,不特别指定的话,将与共享库相连接
优势
- 共享库占用空间比较小, 生成的可执行文件比较小, 即使修改了库中的代码, 只要相对地址/接口保持不变, 则不需要重新链接.
劣势
- 使用共享库的代码在运行时需要依赖共享库, 并且执行效率较静态库低。
生成共享库
- 编写.c文件 $vi add.c
- 生成.o文件 $cc -c -fpic add.c
- 生成共享库文件 $cc -shared -o libadd.so add.o
- 链接共享库 $cc -o main main.o -ladd -L.
Q:如果本文件夹有了libdl.a
或者libdl.so
会链接本文件夹的还是系统默认的???
A:如果使用$gcc main.o -L. -ldl
当然会链接本文件夹的, 你当-L.是空气啊
Note:
- -fpic是生成位置无关码的选项, 即生成相对地址
$ldd a.out
#查看a.out所依赖的共享库信息- 对于临时使用的不在公用库目录的共享库,链接后可以把库的路径添加到LD_LIBRARY_PATH:
$export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:`pwd`
- 如要永久更改动态库的搜索目录,可以创建文件
/etc/ld.so.conf.d/my.config
并将路径写入其中,再使用$ldconfig /etc/ld.so.conf.d/my.config
配置链接选项 - 与静态库不同,修改环境变量和配置文件都是用来解决运行时找不到库的问题,并不针对编译时链接的问题,编译时还是要使用
-L.
静态链接
静态链接,即编译时链接,使用-static可以强制链接静态库
Q:静态链接时调用函数需要包含相应库的头文件吗?
A:需要. 链接我们熟知的#include<stdio.h>...
对应的libc.so
时就是使用的静态链接, 默认都是静态链接共享库,也可以使用$gcc –static
强制链接静态库libc.a
,任何一个库都有两个版本:.a版本和.so版本
静态链接:
- 编写.c文件 $vi main.c
- 生成.o文件 $cc -c main.c
- 链接库文件 $cc main.o –ladd
Note:
- 开发时,使用静态链接,以便gcc能够找到编译时需要的共享库。
- 发布时,使用动态链接,以便程序加载运行时能够自动找到需要的共享库。
动态链接
动态链接:运行时链接,建立在静态链接libdl.so(a)库的基础上, 程序在运行过程中动态地链接共享库,所以需要在源代码中写链接代码,编译器是无能为力了,只能帮到dl库了
Q:动态链接时调用函数需要包含相应库的头文件吗?
A:动态链接libadd.so是建立在静态链接 libdl.so的基础上的, 既然后者是静态链接, 当然还需要包含相应的<dlfcn.h>,但libadd.so里的函数在调用时就不需要相应的头文件了
Q:-ldl是不是因为使用的<dlfcn.h>???
A:是,man dlopen,这个库包含实现动态链接的代码
ATENTION:动态链接和静态链接不是并列关系,是依存关系, 没有静态链接的libdl.so(a), 动态链接就是个屁
Q: 为什么共享库要有执行权限, 静态库不需要
A:因为共享库在运行时使用,静态库在编译时里面的代码已经链接到程序里了,运行时和静态库就没关系了
动态连续需要静态链接dl.so库:
- 编写.c文件 $vi main.c
- 生成.o文件 $cc -c main.c
- 链接库文件 $cc main.o –ldl
动态链接的源文件
#include<stdio.h>
#include<stdlib.h>
#include<dlfcn.h>
int main(){
void *handle=dlopen("../libfcn.so",RTLD_NOW);
if(NULL==handle)
printf("%s",dlerror()), exit(-1);
int (*pAdd)(int,int)=(int (*)(int,int))dlsym(handle, "add");
if(NULL==pAdd)
printf("%s",dlerror()),exit(-1);
printf("%d\n",pAdd(1,2));
int res=dlclose(handle);
if(0!=res)
printf("%s",dlerror()),exit(-1);
return 0;
}
Note:
- -l是静态链接库名选项, dl是共享库名(libdl.so), 用来实现我们程序中的动态加载卸载libadd.so