头文件,链接库,编译汇总

在linux下代码开发过程中,一个程序从编译生成,到运行过程中必须不能忽略的地方:

                  1.程序在编译时,源码所需要的库(静态库和动态库)及头文件编译器是去哪找的?(库及头文件的查找)

                  2.当输入一个命令时,系统时如何找到这个命令的?(命令的查找)

                  3.程序在运行时,它所需要的库是去哪找的?(动态链接库的查找)

一、头文件查找

1 、#include <>与#include “”

#include <>直接到系统指定的某些目录中去找某些头文件。

#include “”先到源文件所在文件夹去找,然后再到系统指定的某些目录中去找某些头文件。

头文件定义为了防止各个不同函数引用时的冲突可以使用#ifndef #define #endif 模式

例子如下:

     #ifndef _TIME_H_

     #define _TIME_H_ 1

     (头文件声明部分)

     #endif

2、gcc头文件的指定

1.会在默认情况下指定到/usr/include文件夹(更深层次的是一个相对路径,gcc可执行程序的路径是/usr/bin/gcc,那么它在实际工作时指定头文件头径是一种相对路径方法,换算成绝对路径就是加上/usr/include,如#include 就是包含/usr/include/stdio.h)

2.GCC还使用了-I指定路径的方式

gcc -I include_path code.c
举一个例子:
设当前路径为/root/test,其结构如下:
include_test.c
include/include_test.h
有两种方法访问到include_test.h。

1. include_test.c中#include “include/include_test.h”然后gcc include_test.c即可
2. include_test.c中#include 或者#include 然后gcc –I include include_test.c也可(注意-I后面可以加空格也可以不加,不过加空格后可以使用tab的命令补全)

3. 参数:-nostdinc使编译器不再系统缺省的头文件目录里面找头文件,一般和-I联合使用,明确限定头文件的位置。

3、头文件搜索顺序:

1.  首先gcc会从-Idir   -isystem dir   -Bprefix    -sysroot  dir     --sysroot=dir    -iquote dir选项指定的路径查找(这些选项先指定的会先搜索,有特例的情况请参考前面的链接)

2.然后找gcc的环境变量 C_INCLUDE_PATH、CPLUS_INCLUDE_PATH 、CPATH、GCC_EXEC_PREFIX

3.然后查找GCC安装的目录(可以通过gcc  -print-search-dirs查询)

/usr/lib/gcc-lib/i386-linux/2.95.2/include
/usr/lib/gcc-lib/i386-linux/2.95.2/../../../../include/g++-3
/usr/lib/gcc-lib/i386-linux/2.95.2/../../../../i386-linux/include

4.然后再按照下面列出的顺序查找系统默认的目录:/usr/include      /usr/local/include

但是如果装gcc的时候,是有给定的prefix的话,那么就是
/usr/include
prefix/include
prefix/xxx-xxx-xxx-gnulibc/include
prefix/lib/gcc-lib/xxxx-xxx-xxx-gnulibc/2.8.1/include

4、相关环境变量

C_INCLUDE_PATH编译 C 程序时使用该环境变量。

CPLUS_INCLUDE_PATH编译 C++ 程序时使用该环境变量。

CPATH 编译 C 、 C++ 和 Objective-C 程序时使用该环境变量。

以C_INCLUDE_PATH为例子介绍添加环境变量的方法:

export C_INCLUDE_PATH=$C_INCLUDE_PATH:/新的路径

查看环境变量内容 echo $C_INCLUDE_PATH


二、动态链接库shared object(.so)

1、动态链接生成

以为 test.c 生成.so文件为例:
gcc test.c -shared -fPIC -o libtest.so
或者
gcc test.c -fPIC -o test.o
gcc test.o -shared -o libtest.so

动态链接库的后缀为.so 生成文件格式应该为libXXX.so。这样在链接时就可以使用 gcc -ltest 命令链接libtest.so(-l参数会自动省略lib .so部分)

2、编译中动态链接的使用

以 test_lib.c文件调用/backup/mylib文件目录下的libtest.so为例子:
gcc test_lib.c -o test_lib -L /backup/mylib -ltest
-L参数指定编译器搜索的目录 -l指定链接的库文件。

3、环境变量

LD_LIBRARY_PATH调整方式与头文件环境变量调整方式一致

note:现代连接器在处理动态库时将链接时路径(Link-time path)和运行时路径(Run-time path)分开,用户可以通过-L指定连接时库的路径,通过-R(或-rpath)指定程序运行时库的路径,大大提高了库应用的灵活性。(这同时导致了在运行执行文件时会出现链接错误,因为执行文件不知道去哪里找编译时使用的动态链接库)

4、动态链接库命名规则

动态链接一般具有(truename)和(linkername)。 turename用于版本维护格式如下: lib库名.so.version
linkername一般以软连接与true name连接在一起 ln -s truename linkername。可以把linkername 看做时windows中的快捷方式。

5、编译时动态链接库查找顺序

程序在编译链接时,编译器是按照如下顺序来查找动态链接库(共享库)和静态链接库的:
1.  gcc会先按照-Ldir    -Bprefix选项指定的路径查找
2. 再找gcc的环境变量GCC_EXEC_PREFIX
3. 再找gcc的环境变量LIBRARY_PATH(注意区别于LD_LIBRARY_PATH)
4. 然后查找GCC安装的目录(可以通过gcc  -print-search-dirs查询)
5.  然后查找默认路径/lib
6.  然后查找默认路径/usr/lib
7.  最后查找默认路径/usr/local/lib
8.  在同一个目录下,如果有相同文件名的库(只是后缀不同),那么默认链接的是动态链接库,可以用-static选项显示的指定链接静态库。
(编译时,编译器不会查找LD_LIBRARY_PATH,还有/etc/ld.so.conf文件中指定的路径。)

三、静态链接库(.a)

这里只做简单过程描述。
首先,.a库只能添加.o文件,经过测试 生成.o文件时,是否添加 -fPIC参数都可以正常运行。(用.o文件生成.so库时编译生成.o时必须添加参数 -fPIC,但静态库不用)。整个过程代码如下:
1. gcc test.c -c -o test.o //生成.o文件,因为静态库只识别.o文件。
2. ar -rc libtest.a test.o //ar 命令是静态库打包命令,详细可以查阅manuel,这这一步中若是ar -rc libtest.a test.c 也可以成功 但是在下一步编译链接时会无法识别库文件内容
3. gcc test_lib.c -o test_lib -L/lib路径 -ltest //注意 linux下-l参数默认先链接动态库,在动态库不存在的情况下链接静态库,若强制链接静态库需要添加 -static 参数
(gcc test_lib.c -o test_lib -L/lib路径 -static -ltest)


静态库生成的执行文件,相当于完全添加了库代码,所以在今后的执行过程中不在需要链接库文件,这与动态链接库是不同的。


四、当输入一个命令时,系统时如何找到这个命令的?(命令的查找)

    如果我们输入一个命令时带入路径时一般是不会不什么疑问的,因为此时我们执行的就是指定路径下程序。当我们只输入一个命令名时会发生什么情况呢?
当我们键入命令名时,linux系统更确切的说应该是shell按照如下顺序搜索:

1.  Shell首先检查命令是不是保留字(比如for、do等)
2.  如果不是保留字,并且不在引号中,shell接着检查别名表,如果找到匹配则进行替换,如果别名定义以空格结尾,则对下一个词作别名替换,接着把替换的结果再跟保留字表比较,如果不是保留字,则shell转入第3步。
3.  然后,shell在函数表中查找该命令,如果找到则执行。
4.  接着shell再检查该命令是不是内部命令(比如cd、pwd)
5.  最后shell在PATH中搜索以确定命令的位置
6.  如果还是找不到命令则产生“command not found”错误信息。

这里要注意一点:系统在按PATH变量定义的路径搜索文件时,先搜到的命令先执行。例如,我的PATH变量如下:
root@ubuntu:~# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:
如果在不同的目录中有两个ls文件,例如/usr/local/sbin/ls, /usr/local/bin/ls,那么在使用ls的时候,会执行/usr/local/sbin/ls,因为在PATH中哪个目录先被查询,则哪个目录下的文件就会先执行。

五、程序在运行时,它所需要的库是去哪找的?(动态链接库的查找)

    在这里我没有提到头文件的查找,因为头文件只在编译的时候才会用到,编译完后就不需要头文件了!另外,这里的库指的是动态链接库,静态链接库在链接后是不需要了的,因为链接时链接器会把静态库中的代码插入到相应的函数的调用处,所以程序在运行时不再需要静态库,而对于动态库来说,链接时,并没有将动态库中的任何代码或数据拷贝到可执行文件中,而只是拷贝了一些重定位与符号表信息!所以程序在运行时才需要链接时所使用的动态链接库以执行动态链接库中的代码!这个可以参考《深入理解计算机系统》第七章。

    程序运行时动态库的搜索路径搜索的先后顺序是:
1.编译目标代码时指定的动态库搜索路径(指的是用-wl,rpath或-R选项而不是-L);
example: gcc -Wl,-rpath,/home/arc/test,-rpath,/lib/,-rpath,/usr/lib/,-rpath,/usr/local/lib test.c
2.环境变量LD_LIBRARY_PATH指定的动态库搜索路径;
3.配置文件/etc/ld.so.conf中指定的动态库搜索路径;
4.默认的动态库搜索路径/lib;
5.默认的动态库搜索路径/usr/lib。
在上述1、2、3指定动态库搜索路径时,都可指定多个动态库搜索路径,其搜索的先后顺序是按指定路径的先后顺序搜索的。

上面这个的具体内容可以参考:
http://hi.baidu.com/kkernel/blog/item/ce31bb34a07e6b46251f14cf.html

    在这里补充说明下:gcc的-Wl,rpath选项可以设置动态库所在路径,也就是编译生成的该程序在运行时将到-Wl,rpath所指定的路径下去寻找动态库,如果没找到则到其它地方去找,并且这个路径会直接写在elf文件(就是生成的可执行文件)中,这样可以免去设置LD_LIBRARY_PATH。注意,gcc参数设定时-Wl,rpath,/path/to/lib, 中间不能有空格。
gcc -o test test.c -L. -lpos -Wl,-rpath,./
上面这个命令的意思是:编译test.c时在当前目录下查找libpos.so这个库,生成的文件名为test,当执行test这个文件时,在当前目录下查找所需要的动态库文件。
可以像下面这个命令一样指定查找多个路径:
gcc -Wl,-rpath,/home/arc/test,-rpath,/lib/,-rpath,/usr/lib/,-rpath,/usr/local/libtest.c

更改/etc/ld.so.conf文件后记得一定要执行命令:ldconfig!该命令会将/etc/ld.so.conf文件中所有路径下的库载入内存中。


下面对编译时库的查找与运行时库的查找做一个简单的比较: 1. 编译时查找的是静态库或动态库,而运行时,查找的只是动态库。 2. 编译时可以用-L指定查找路径,或者用环境变量LIBRARY_PATH,而运行时可以用-Wl,rpath或-R选项,或者修改/etc/ld.so.conf文件或者设置环境变量LD_LIBRARY_PATH. 3. 编译时用的链接器是ld,而运行时用的链接器是/lib/ld-linux.so.2. 4. 编译时与运行时都会查找默认路径:/lib  /usr/lib 5. 编译时还有一个默认路径:/usr/local/lib,而运行时不会默认找查该路径。     如果安装的包或程序没有放在默认的路径下,则使用mancommand查找command的帮助时可能查不到,这时可以修改MANPATH环境变量,或者修改/etc/manpath.config文件。如果使用了pkg-config这个程序来对包进行管理,那么有可能要设置PKG_CONFIG_PATH环境变量,这个可以参考:http://www.linuxsir.org/bbs/showthread.php?t=184419 写在最后的话     一个程序的从生到死会发生很多很多的故事,在这里,我只是从一个角度探讨了其中的冰山一角,还有许许多多的问题需要去理解,比如说:编译链接时,各个文件是如何链接到一起的?程序运行时,动态库已经被加载到内存中,程序又是如何准确找到动态库在内存中的位置的?动态库的链接器/lib/ld-linux.so.2自己本身也是一个动态库,那么它又是如何被载入内存的呢?更深入的想一下,可以认为ld-linux.so.2是随内核一起载入内存的,那内核又是如何载入内存的呢?如果说内核是由bootloader载入的,那bootloader又是如何载入内存的呢?也许你该想到BIOS了。其中的一些问题可以参考《深入理解计算机系统》这本书。

相关文章
相关标签/搜索