查找C语言头文件、库文件路径的书写格式

在程序设计中,文件包含是很有用的。一个大的程序可以分为多个模块,由多个程序员分别编程。有些公用的符号常量或宏定义等可单独组成一个文件,在其它文件的开头用包含命令包含该文件即可使用。这样,可避免在每个文件开头都去书写那些公用量,从而节省时间,并减少出错。

对文件包含命令还要说明以下几点:
1. 包含命令中的文件名可以用双引号括起来,也可以用尖括号括起来。例如以下写法都是允许的:
    #include"stdio.h"
    #include
    但是这两种形式是有区别的:使用尖括号表示在包含文件目录中去查找(包含目录是由用户在设置环境时设置的),而不在源文件目录去查找;
    使用双引号则表示首先在当前的源文件目录中查找,若未找到才到包含目录中去查找。用户编程时可根据自己文件所在的目录来选择某一种命令形式。
2. 一个include命令只能指定一个被包含文件,若有多个文件要包含,则需用多个include命令。
3. 文件包含允许嵌套,即在一个被包含的文件中又可以包含另一个文件。

a. include<头文件名>和include"头文件名"

如:include和include"stdio.h"

前者(使用<>),来引用stdio.h文件,是首先检索标准路径,看看这些文件夹下是否有该头文件;如果没有,也不会检索当前文件所在路径,并将报错。

后者(使用""),来引用stdio.h文件,是首先检索文件的当前路径;如果没有,再检索标准路径,看看这些文件夹下是否有该头文件。

b. linux下,上述标准路径有:/usr/include,/usr/local/include。

c. ,等。其中,前面的字符串(如sys,net)表示标准路径下的文件夹名,后面的字符串(如io.h,ethernet.h),表示在linux标准路径下的各文件夹下的头文件名,如sys文件夹下的io.h文件,即我们可以在/usr/include/sys目录下发现io.h文件。

linux博大精深,需要慢慢积累。

d. 如果想在指定路径下检索头文件,可加选项-I。如我的/home/Desktop目录下有个头文件local1.h,在编译包含local1.h的test.c文件时,可用:gcc test.c -o test -I /root/Desktop。 

一、讨论环境

*操作系统:Redhat5/Fedora14

*编译器:gcc 4.5.1

以下言论仅确保在以上环境中适用。别的环境,大家可以通过类比方法,得到启示。

二、C语言头文件的查找路径

C语言,使用include指令,包含头文件,但又细分两种形式:

1、形式一:#include “file1”
  gcc先在当前目录(指包含本条#include指令的源文件所在的目录)寻找file1,如果找不到,继续在由-iquote选项(如果有的话)指定的目录中寻找file1。

  例如,在文件/usr/include/sys/stat.h中,包含指令#include “types.h”,那么gcc先在/usr/include/sys目录下寻找types.h文件。嗯,在该目录下,确实存在一个types.h的文件。现假设我们把这个文件移动到另一个目录:mv/usr/include/sys/types.h /bar/foo/,我们在编译时,可以通过-iquote选项,在不改变stat.h的情况下,正常编译(当然,通常不建议这样做):gcc -iquote /bar/foo -I/usr/include/sys *.o

2、形式二:#include 
  gcc按照以下顺序查找file2:-Idir1 -Idir2 ... 

/usr/local/include 

libdir/gcc///include 

/usr//include 
/usr/include 
第一行中,-Idir1 -Idir2 ... 是用户通过gcc的-I选项指定的目录。值得一提的是,放在/usr/local/include/下的头文件也会被gcc自动的检索,这与/usr/local/lib/目录下的库处理方式是不一样的(gcc的链接器在运行时阶段不会自动查找该目录下的库文件,下一节会提到)。

 


三、C语言库文件的查找路径
C语言库文件的查找路径,又分为两个阶段:链接阶段、运行时阶段。

1、链接阶段(link time)
  此阶段,需要告诉编译器,在哪里找到库文件?以静态还是动态的方式链接库文件?默认情况下使用动态方式链接,这要求存在对应的.so动态库文件,如果不存在,则寻找相应的.a静态库文件。若在编译时向gcc传入-static选项,则使用静态方式链接,这要求所有库文件都必须有对应的*.a静态库。

  那么,是否可以令某些库使用动态链接,另一些库使用静态链接?不太确定,请参考ld的使用手册,我没有这样用过。

  切入正题,在链接阶段,gcc编译器如何寻找库文件呢(linker本身并没有默认的查找路径,这些查找路径是由gcc传递给linker的)?大家可以在编译时,向gcc加入-v选项来观察它向linker传递的库文件查找路径(观察LIBRARY_PATH变量的值),通常查找路径如下:

任何由-rpath-link或-rpath选项指定的目录 
LD_RUN_PATH(如果没有找到-rpath或-rpath-link选项) 
-Ldir1 -Ldir2 ... 
/usr/lib/gcc/// 
/usr/lib/ 
第一行-rpath-link与-rpath选项的区别在于,-rpath选项指定的目录被硬编码到可执行文件中,-rpath-link选项指定的目录只在链接阶段生效。由于这两个选项都是链接器ld的选项,如何从gcc中向ld传递这两个选项?方法如下(更从细节参考gcc的-Wl选项):

gcc -Wl, -rpath, /usr/local/lib

这相当于向ld向传递了如下参数:

ld -rpath /usr/local/lib

第二行,如果没有设置-rpath或-rpath-link选项,则查找LD_RUN_PATH环境变量指定的目录,并把它当作-rpath选项来处理。第三行-Ldir1 -Ldir2 ...,是我们通过gcc的-L选项向其指定的库文件查找路径,查找顺序按照我们传递的-L参数从左到右进行搜索;第四行属于gcc自己的库目录;第五行/usr/lib/是Linux系统默认的系统库文件的目录。第四、第五行,都是gcc自动向linker传递的查找目录。例如我现在的机器上,使用gcc -v可以看到LIBRARY_PATH变量值为:

LIBRARY_PATH=/usr/lib/gcc/i686-redhat-linux/4.5.1/:/usr/lib/

但是这并不是全部真理!根据我自己的测试,我发现gcc会把/usr/local/lib/目录也作为链接阶段的查找路径,这正是问题的根源——我们在链接过程中,使用到了/usr/local/lib/里面的一些库文件,但在运行时阶段,却说找不到该库文件。

2、运行时阶段(runtime)
  仅当可执行程序采用动态的方式链接库文件时,才会存在运行时库文件的查找问题。对于这种可执行程序,它本身只是记录动态库的名称。所以在运行该程序时,操作系统的加载程序(ld.so)需要根据库的名称,在必要时加载库文件到内存中。

  在linux中,在运行时阶段,动态库(又叫共享库)的查找路径如下:

-rpath选项指定的目录(已被硬编码到可执行文件中) 
LD_LIBRARY_PATH 
/lib或/usr/lib 
系统默认的查找路径 
我们可以通过readelf查看被硬编码到可执行文件中的rpath:

$ readelf -d <可执行文件名>                #Display the dynamic section (if present)

LD_LIBRARY_PATH则没有这个问题,但是通常我们不建议使用这个环境变量,因为修改这个变量意味着影响所有依赖于这个环境变量的程序(如果非要使用,请把这个环境变量写在启动脚本中,并且让它只影响脚本中的程序)。

  那么系统默认的查找路径又是怎样的?在Redhat5/Fedora14中,ld.so通过读取/etc/ld.so.cache文件来查找库文件的位置,如果没有找到则继续从/etc/ld.so.conf文件中指定的目录查找。这个ld.so.cache文件相当于一个key-value的数据库,key就是动态库的名称,value就是这些库的存放路径。

  那么/etc/ld.so.cache文件是怎么生成的呢?这就要谈到ldconfig这个工具程序了。ldconfig是动态链接库的配置工具,使用它可以更新/etc/ld.so.cache文件,也可以查看这个文件中的key-value信息(使用ldconfig -p),ldconfig的使用细节,请参考它的使用手册。总结一下系统默认的查找路径:

/etc/ld.so.cache 
/etc/ld.so.conf文件中指定的目录 
四、参考资料
man ld 
man ldconfig 
http://gcc.gnu.org/onlinedocs/cpp/Search-Path.html 
http://www.eyrie.org/~eagle/notes/rpath.html 



本文介绍在linux中头文件的搜索路径,也就是说你通过include指定的头文件,linux下的gcc编译器它是怎么找到它的呢。在此之前,先了解一个基本概念。

    头文件是一种文本文件,使用文本编辑器将代码编写好之后,以扩展名.h保存就行了。头文件中一般放一些重复使用的代码,例如函数声明、变量声明、常数定义、宏的定义等等。当使用#include语句将头文件引用时,相当于将头文件中所有内容,复制到#include处。#include有两种写法形式,分别是:

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

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

    

    #include文件可能会带来一个问题就是重复应用,如a.h引用的一个函数是某种实现,而b.h引用的这个函数却是另外一种实现,这样在编译的时候将会出现错误。所以,为了避免因为重复引用而导致的编译错误,头文件常具有:

#ifndef    LABEL

#define    LABEL

    //代码部分

#endif

的格式。其中LABEL为一个唯一的标号,命名规则跟变量的命名规则一样。常根据它所在的头文件名来命名,例如,如果头文件的文件名叫做hardware.h,那么可以这样使用:

#ifndef    __HARDWARE_H__

#define    __HARDWARE_H__

  //代码部分

#endif

这样写的意思就是,如果没有定义__HARDWARE_H__,则定义__HARDWARE_H__,并编译下面的代码部分,直到遇到#endif。这样当重复引用时,由于__HARDWARE_H__已经被定义,则下面的代码部分就不会被编译了,这样就避免了重复定义。

 

    一句话,头文件事实上只是把一些常用的命令集成在里面,你要用到哪方面的命令就载入哪个头文件就可以了。

 

    gcc寻找头文件的路径(按照1->2->3的顺序)

    1. 在gcc编译源文件的时候,通过参数-I指定头文件的搜索路径,如果指定路径有多个路径时,则按照指定路径的顺序搜索头文件。命令形式如:“gcc -I /path/where/theheadfile/in sourcefile.c“,这里源文件的路径可以是绝对路径,也可以是相对路径。eg:

设当前路径为/root/test,include_test.c如果要包含头文件“include/include_test.h“,有两种方法:

1) include_test.c中#include “include/include_test.h”或者#include "/root/test/include/include_test.h",然后gcc include_test.c即可

2) include_test.c中#include 或者#include ,然后gcc –I include include_test.c也可

 

    2. 通过查找gcc的环境变量C_INCLUDE_PATH/CPLUS_INCLUDE_PATH/OBJC_INCLUDE_PATH来搜索头文件位置。

 

    3. 再找内定目录搜索,分别是

/usr/include

/usr/local/include

/usr/lib/gcc-lib/i386-linux/2.95.2/include

最后一行是gcc程序的库文件地址,各个用户的系统上可能不一样。

    gcc在默认情况下,都会指定到/usr/include文件夹寻找头文件。

    gcc还有一个参数:-nostdinc,它使编译器不再系统缺省的头文件目录里面找头文件,一般和-I联合使用,明确限定头文件的位置。在编译驱动模块时,由于非凡的需求必须强制GCC不搜索系统默认路径,也就是不搜索/usr/include要用参数-nostdinc,还要自己用-I参数来指定内核头文件路径,这个时候必须在Makefile中指定。


    4. 当#include使用相对路径的时候,gcc最终会根据上面这些路径,来最终构建出头文件的位置。如#include 就是包含文件/usr/include/sys/types.h

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CATHY_T

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值