`
shixiaomu
  • 浏览: 382556 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

linux C 编译全生命周期详解

 
阅读更多

程序编译运行时头文件或动态链接库的查找
[此博文包含图片] (2013-06-05 14:08:10)
转载▼
标签:
动态库
头文件
查找
it
分类: linux

转自:http://blog.csdn.net/dlutxie/article/details/6776936

          当考虑怎样总结这个头文件及动态链接库的查找问题时,我想到了一个程序从生到死的历程。写过很多程序,编译过很多程序,也运行过很多程序,对一个程序的从生到死,感觉很简单,也就没有做更多的或者说深入的思考与研究。也许我们习惯了在windows环境下的编程,在那里我们有很好的IDE,它能把一个工程组织得很好,直接点编译生成一个可执行文件,然后直接双击这个.exe文件或者创建一个快捷方式运行这个程序。以前可能我们也听说过,源码先要编译,然后链接,然后装载、运行,可我们很少去考虑这背后到底都发生了些什么,似乎也不用考虑那么多,因为我们的IDE实在是太智能了,因为我们已经很习惯了使用windows环境,因为在windows下安装个软件,我们几乎只需要点击下一步就可以了。

        这段时间在做linux系统下opencv2.0到ARM开发板的移植,在这里,我有很多问题不得不考虑,问题如下:

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

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

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

       这就是一个程序的由生到死的过程中需要考虑的几个问题!

       在linux系统下,我们常常要自己通过源码安装一些库,装一些软件,第一件事该想到的,编译生成后的头文件,库或者程序我们该放到哪,是放到/lib  /usr/lib  /usr/include  /usr/bin  /usr/local/lib  /usr/local/include    /usr/local/bin等目录下吗?可能有些书会说自己安装的程序一般放在/usr/local目录下,放到这下面我可以省心的不用去修改一些环境变量或配置文件了,但接下来我们可能会想,要是我以后想删除我前面安装的软件呢?你还能想起你以前安装这个软件时它到底安装了哪些文件了吗?几乎不可能吧!

         所以当我想安装一个软件时我希望像在windows下一样,把这个软件安装在一个单独的目录下,比如说我要安装opencv2.0,那么我就在/usr/local目录下创建一个目录opnecv2.0,然后把所有相关的都安装到/usr/local/opencv2.0目录下,这样如果我以后不想要这个库时我就可以直接删掉这个文件夹就可以了。可在这里我们就有些问题不得不考虑了:如果我们把opencv安装到了/usr/local/opencv2.0这个目录下了,那编译器在编译包含有opencv2.0的库或头文件时,编译器能找着这些头文件和库吗?如果这是一个可运行文件,我在其它目录下运行这个文件,系统能找到这个文件吗?它是如何找到这个文件的呢?当其它包含有opencv有关函数的程序时,它是如何找到这些库的呢?由这就引发了我上面提到的三个问题。

         下面我们先来看第一个问题:程序在编译时,源码所需要的库及头文件编译器是去哪找的?

         在这里,其实有库的查找,和头文件的查找,下面先来讲头文件的查找。我们在写一个比较大型的程序时,总是喜欢把这些函数还有一些数据结构的声明放在一个文件中,我们把这种文件称为头文件,文件名以.h后缀结尾。在一些源文件里,我们可能要包含自己写的头文件,还有一些标准库的头文件比如说stdio.h等等。在编译的预处理阶段,预处理程序会将这些头文件的内容插到相应的include指令处,现在的问题是编译器是如何找到这些头文件的。

         1. 在编译时,我们可以用-I(i的大写)选项来指定头文件所在的目录,如:

    test.h内容如下:
    [cpp] view plaincopy

        Struct student 
        { 
            int  age; 
        }; 

    main.c 内容如下:
    [cpp] view plaincopy

        #include 
        #include 
        int main() 
        { 
            structstudent st; 
            st.age= 25; 
            printf(“st.age=%d\n”,st.age); 
            return0; 
        } 


         可以把test.h放在与main.c同一个目录下,编译命令如下:

        xgy@ubuntu:~/tmp/workSpace/testincludedir$  gcc main.c -I./

        如果把test.h放在/usr/include/xgytest目录下,注意,xgytest是我自己建的一个目录

       编译命令如下:xgy@ubuntu:~/tmp/workSpace/testincludedir$  gcc main.c –I/usr/include/xgytest

       注意:在-I后可以有空格也可以没有空格,另外也可以指定多个目录,例如,tesh.h放在当前文件夹下,还有一个teacher.h放在 ./include目录下,则可以这样编译:

      xgy@ubuntu:~/tmp/workSpace/testincludedir$  gcc main.c -I ./ -I ./include/

           2. 设置gcc的环境变量C_INCLUDE_PATH、CPLUS_INCLUDE_PATH 、CPATH。

        C_INCLUDE_PATH编译 C 程序时使用该环境变量。该环境变量指定一个或多个目录名列表,查找头文件,就好像在命令行中指定 -isystem 选项一样。会首先查找 -isystem 指定的所有目录。

         CPLUS_INCLUDE_PATH编译 C++ 程序时使用该环境变量。该环境变量指定一个或多个目录名列表,查找头文件,就好像在命令行中指定 -isystem 选项一样。会首先查找 -isystem 指定的所有目录。

          CPATH 编译 C 、 C++ 和 Objective-C 程序时使用该环境变量。该环 境变量指定一个或多个目录名列表,查找头文件,就好像在命令行中指定-l 选项一样。会首先查找-l 指定的所有目录。

          假设test.h放在/usr/include/xgytest,则对C_INCLUDE_PATH做如下设置:

export C_INCLUDE_PATH=$C_INCLUDE_PATH:/usr/include/xgytest

详细请况可以参考如下文章:

http://blog.csdn.net/katadoc360/article/details/4151286

http://blog.csdn.net/dlutxie/article/details/8176164

3. 查找默认的路径/usr/include   /usr/local/include等



总结一下gcc在编译源码时是如何寻找所需要的头文件的:

   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查询)

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

        

程序在编译时,编译器又是如何查找所需要的库的呢?这里的库既包括静态库又包括动态库。在这里,我们得先了解两个概念:库的链接时路径和运行时路径。

        现代连接器在处理动态库时将链接时路径(Link-time path)和运行时路径(Run-time path)分开,用户可以通过-L指定连接时库的路径,通过-R(或-rpath)指定程序运行时库的路径,大大提高了库应用的灵活性。

我们来看几个例子:

pos.c文件的内容如下:


[cpp] view plaincopy

    #include 
    void pos() 
    { 
        printf("the directory is .//n"); 
    } 



main.c文件的内容如下:


[cpp] view plaincopy

    #include 
    intmain() 
    { 
        pos(); 
        return 0; 
    } 



接下来看如下执行的命令:

我们来分析下上面图片中的命令:生成的动态链接库libpos.so放在了当前的路径下,接着用gcc main.c  –lpos 来链接这个库却发现ld找不着这个库!然后我加了一个-L选项,指出这个库在当前路径下,结果编译通过,可在运行刚编译生成的a.out时又出现了错误!这就是运行是的链接错误!运行时的链接问题在后面将有介绍。用ldd命令可以查看一个可执行文件依懒于哪些库。注意-lpos, 这里的-l是L的小写,另外也可以写成-l  pos即中间有一个空格,但有没有空格是有一点区别的,有空格的只搜索与POSIX兼容的库,一般建议使用没有空格的。

         另外我们可以把刚才编译生成的libpos.so拷到默认的路径/lib  /usr/lib /usr/local/lib路径下,然后直接执行gcc main.c –lpos也可以通过编译。

         在这里补充说明一点:Linux下 的库文件在命名时有一个约定,那就是库文件应该以lib三个字母开头,由于所有的库文件都遵循了同样的规范,因此在用-l(L的小写字母)选项指定链接的库文件名时可以省去 lib三个字母,也就是说GCC在对-lfoo进行处理时,会自动去链接名为libfoo.so的文件。

    每个共享库也有一个实名,其真正包含有库的代码,组成如下:

    so名+.+子版本号+.+发布号(最后的句点和发布号是可选项。)

    另外,共享库还有一个名称,一般用于编译连接,称为连名(linkername),它可以被看作是没有任何版本号的so名。在上面的讨论中,我一直是以动态库(或者说共享库)为例的,其实对于静态库也一样,只是在这里又有一个问题,如果在同一个目录下既有动态库,又有静态库,且它俩的文件名也一样,只是后缀不一样,那链接器在链接时是链接动态库还是链接静态库呢?如果我要指定链接动态库或者静态库又该如何做呢?

           让我们来看看下面执行的命令(注意下,为了方便,我开了两个终端):

通过上面的这些命令,也许就能回答我刚提出的两个问题了。

在这里,我还想看下编译时gcc是否会查LD_LIBRARY_PATH环境变量,还有/etc/ld.so.conf文件指定的路径,命令如下:

从上面的命令可以看出,编译时,编译器不会查找LD_LIBRARY_PATH,还有/etc/ld.so.conf文件中指定的路径。下面来总结下:



程序在编译链接时,编译器是按照如下顺序来查找动态链接库(共享库)和静态链接库的:

    1.  gcc会先按照-Ldir    -Bprefix选项指定的路径查找

    2. 再找gcc的环境变量GCC_EXEC_PREFIX

    3. 再找gcc的环境变量LIBRARY_PATH

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

    5.  然后查找默认路径/lib

    6.  然后查找默认路径/usr/lib

    7.  最后查找默认路径/usr/local/lib

    8.  在同一个目录下,如果有相同文件名的库(只是后缀不同),那么默认链接的是动态链接库,可以用-static选项显示的指定链接静态库。

    

第二个问题:当输入一个命令时,系统时如何找到这个命令的?(命令的查找)

        如果我们输入一个命令时带入路径时一般是不会不什么疑问的,因为此时我们执行的就是指定路径下程序。当我们只输入一个命令名时会发生什么情况呢?

当我们键入命令名时,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 pos main.c -L. -lpos -Wl,-rpath,./

    上面这个命令的意思是:编译main.c时在当前目录下查找libpos.so这个库,生成的文件名为pos,当执行pos这个文件时,在当前目录下查找所需要的动态库文件。

可以像下面这个命令一样指定查找多个路径:

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

分享到:
评论

相关推荐

    Linux内核编译配置选项详解

    ### Linux内核编译配置选项详解 #### 一、代码成熟度选项(Code maturity level options) 在内核编译过程中,存在一个重要的配置类别被称为“代码成熟度选项”。这部分配置主要涉及那些仍在开发阶段或者尚未完全...

    Linux内核编译配置选项详解[定义].pdf

    - **Export task/process statistics through netlink**:通过netlink接口提供更详细的统计信息,整个任务/进程生命周期都可用。 - **Per-task delay accounting**:统计进程等待系统资源的时间,有助于分析系统...

    嵌入式Linux应用程序开发详解(完整版)

    例如,理解进程的生命周期、如何创建和管理进程,以及如何有效地利用内存资源,都是开发高效程序的必备知识。 其次,嵌入式Linux的特性会得到特别强调。嵌入式系统通常资源有限,因此了解如何优化代码以适应这些...

    Linux菜鸟过关+Linux程序指南+Linux系统命令及使用详解

    此外,Linux还提供了一套完整的开发工具链,包括版本控制系统(如Git)、构建工具(如Make)、调试工具等,这为团队协作和软件生命周期管理提供了强大支持。 在深入学习Linux的过程中,理解权限管理、进程管理、...

    linux下c的学习

    进程是操作系统中运行程序的实例,了解进程的生命周期、状态转换、进程间通信(IPC)等概念对于编写高效的C语言程序至关重要。 2. **文件操作** 文件操作包括文件的打开、读写、关闭等操作,是任何应用程序的核心...

    linux_内核编译配置选项

    ### Linux内核编译配置选项详解 #### 引言 Linux内核的编译配置是构建稳定、高效操作系统的关键步骤。正确配置内核不仅能够确保系统的稳定运行,还能显著提升性能,满足特定硬件和应用需求。本文将深入解析《Linux...

    Linux内核编译配置选项简介

    ### Linux 2.6.19.x 内核编译配置选项详解 #### 一、引言 在深入探讨Linux 2.6.19.x内核编译配置选项之前,我们先简单回顾一下内核编译配置的重要性。内核是操作系统的核心组件,负责管理和协调计算机硬件资源以及...

    嵌入式Linux应用程序开发详解第1章Linux 快速入门_linux_boneinn_源码

    3. **进程管理**:理解进程的生命周期,学习如何创建、控制和查看进程状态,例如使用`ps`、`kill`和`nohup`命令。 4. **文件系统**:介绍Linux的文件系统结构,如 `/bin`、`/usr` 和 `/etc` 目录的作用,以及如何...

    linux驱动开发详解

    《Linux驱动开发详解》这本书是针对Linux内核驱动程序开发的专业指南,由宋宝华编著,旨在帮助读者深入理解Linux驱动程序的工作原理,并提供实践经验。书中的实例丰富,覆盖了驱动开发的各个方面,使读者能够通过...

    嵌入式开发学习路线全生命周期规划

    ### 嵌入式开发学习路线全生命周期规划 在当今技术快速发展的时代,嵌入式系统作为连接物理世界与数字世界的桥梁,在多个行业中扮演着至关重要的角色。对于想要进入这一领域的学习者而言,制定一份全面的学习计划至...

    Linux 2.6内核编译配置选项简介.

    ### Linux 2.6 内核编译配置选项详解 #### 概述 Linux 2.6 内核是Linux发展历史上的一个重要里程碑,它带来了许多改进和新特性,特别是对于服务器和桌面环境的支持得到了显著增强。对于内核开发者和系统管理员来说...

    Linux_Kernel核心中文手册(内核图解).pdf,linux内核结构详解,C,C++

    10. **模块化设计**:内核模块的编译、加载与卸载,以及模块在内核中的生命周期管理。 通过阅读这份手册,你不仅可以了解Linux内核的基本工作原理,还可以深入理解C和C++在系统编程中的应用。同时,对于那些想要...

    linux操作系统下c语言编程入门

    - 进程的生命周期、状态变化及其管理是理解Linux操作系统的重要方面。 - 相关API函数: - `fork()`:创建子进程。 - `exec()`系列:替换当前进程的执行上下文。 - `wait()`/`waitpid()`:等待子进程结束。 - `...

    嵌入式linux应用程序开发详解

    同时,设计可靠的固件更新机制,如OTA(Over-The-Air)更新,是维护设备生命周期的重要环节。 学习嵌入式Linux应用程序开发需要扎实的计算机科学基础,包括操作系统原理、数据结构、网络和硬件知识。实践是提高技能...

    linux c学习笔记

    这有助于解决多文件项目中变量的可见性和生命周期问题。 七、浅谈动态内存 动态内存管理是C语言中的一大特性,通过malloc()、calloc()、realloc()和free()等函数进行内存的分配和释放。动态内存可以在程序运行时按...

    嵌入式Linux应用开发详解.rar

    6. **进程管理与调度**:理解Linux进程的生命周期,以及如何通过fork、exec和exit等函数创建和控制进程。同时,学习线程(thread)的概念,包括互斥锁、信号量和条件变量等同步机制,这对于多线程应用的开发非常重要...

Global site tag (gtag.js) - Google Analytics