头文件与库文件

头文件与库文件

头文件

头文件是C/C++语言家族中所采用的,不是所有的语言都有头文件,例如

为什么Java没有头文件

https://blog.csdn.net/aizhenif53568/article/details/101535835

java中采用import的方式导入.class文件,.class文件中包含类的方法、属性等的定义,因此不在需要.h头文件,而c或c++编译后的二进制代码不包含函数和类的信息,为了在编译时进行函数格式合法性检查,采用.h的文件给出。

java提供给用户的是.class文件,虽然包含类的完整信息,但是对于开发者“不可读”,因此还需要以文档的方式提供类的声明。

在编程过程中,程序代码往往被拆成很多部分,每部分放在一个独立的源文件中,而不是将所有的代码放在一个源文件中。考虑一个简单的小例子:程序中有两个函数main()和abc()。main()函数位于main.cpp,abc()函数位于abc.cpp,main()函数中调用abc()函数。在编译阶段,由于编译是对单个文件进行编译,所以编译main.cpp时,编译器不知道是否存在abc()函数以及abc()调用是否正确,因此需要头文件辅助。也就是说,在编译命令:
cl.exe /c main.cpp
运行时,编译器不知道abc的用法是否正确(因为abc在另一个文件abc.cpp中),只有借助头文件中的函数声明来判断。对main.cpp进行编译时,不会涉及abc.cpp文件,只会涉及main.cpp 和abc.h(因为abc.h被include)文件。

库文件

注意,库文件和具体的编程语言及编译器无关,只要遵循约定的DLL接口规范和调用方式,用各种语言编写的DLL都可以相互调用。譬如Windows提供的系统DLL(其中包括了Windows的API),在任何开发环境中都能被调用,不在乎其是Visual Basic、Visual C++还是Delphi。本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。

由于windows和linux的平台不同(主要是编译器、汇编器和连接器的不同),因此二者库的二进制是不兼容的。

库文件中包含一系列的子程序。例如abc.cpp 源文件中实现了abc()函数,我们假设abc()函数是包含重要算法的函数,我们需要将abc()函数提供给客户使用,但是不希望客户看到算法源代码。为了达到这一目的,我们可以将abc.cpp编译程库文件,库文件是二进制的,在库文件中是看不到原始的源代码的。库和可执行文件的区别是,库不是独立程序,他们是向其他程序提供服务的代码。 当然使用库文件的好处不仅仅是对源代码进行保密,使用库文件还可以减少重复编译的时间,增强程序的模块化。将库文件连接到程序中,有两种方式,一种是静态连接库,另一种是动态连接库。

此外还有一些其他写的还不错的博客:
http://blog.chinaunix.net/uid-27575921-id-4078661.html
http://blog.163.com/zhangmaochu@126/blog/static/37360622201076055449/
http://blog.csdn.net/high_high/article/details/7193264(linux下的库文件)

——————-转载分界线——————–
知友回答:

简单来说:库文件通过头文件向外导出接口。用户通过头文件找到库文件中
头文件中有函数的申明,库文件实现函数的定义。
比如,printf函数。使用时应包括stdio.h,打开stdio.h你只能看到,printf这个函数的申明,却看不到printf具体是怎么实现的,而函数的实现在相应的C库中。而库文件一般是以二进制形式而不是C源文件形式提供给用户使用的。程序中包括了stdio.h这个头文件。链接器就能根据头件中的信息找到printf这个函数的实现并链接进这个程序代码段里。
函数实现的代码从而把这段代码链接到用户程序中去。

库文件可以包含头文件,头文件不可包含库文件
头文件可视,库文件不可视

知友回答:
从编程的角度讲,通常有静态库文件和动态库文件。windows静态库文件就是.lib文件,动态库文件就是.dll文件。
内容一样,都是将函数封装在一起编译后供自己或他人调用。好处在于编译后的库文件看不到源代码,可保密;同时不会因为不小心修改了函数而出问题,便于维护。
两种库的区别在于静态库被调用时直接加载到内存,而动态库再是在需要的时候加载到内存,不使用的时候再从内存释放。

unix系统的静态库文件和动态库文件后缀分别是.a和.so

详细讲解静态链接库、动态链接库和动态加载库

注意下面的.so和.a都是linux平台下的写法

静态链接库

前言

静态库是obj文件的一个集合(目标文件中通常仅解析了文件内部的变量和函数,对于引用的函数和变量还没有解析,这需要将其他已经编写好的目标文件引用进来,将没有解析的函数和变量进行解析,通常引用的目标是库),通常静态库以".a"为后缀,名字格式一般为libxxx.a静态库由程序ar生成

实例程序如下:

Main.c

#include <stdio.h>

extern void print_hello();

int

main(void)

{

print_hello();

}

Print_hello.c

#include <stdio.h>

void

print_hello()

{

printf("hello\n");

}

  1. 生成静态链接库

    创建静态库的步骤:

    1. 生成目标文件。(使用命令gcc –c file.c)
    2. 使用工具ar对目标文件进行归档。(使用的命令如下)

    生成静态链接库,或者将一个obj文件加入到已经存在的静态库的命令格式为:

    ar –rcs 库文件obj_1 obj_2 …

    使用上面的实例程序print_hello.c创建静态链接库:

    img

  2. 使用静态链接库

    使用方式一:

    img

    使用方式二:

    img

    注意,在方法二中"-L./"不可少,否则出现如下错误:

    img

    这是因为上面的命令在系统默认的路径下查找hello函数库,而我们并没有将libhello.a库放在系统默认搜索路径下,所以需要显示指定库函数的路径为当前目录。

    另外还需注意,在使用-l选项时,-o选项的目标名称要在-l链接的库名称之前,否则gcc会认为-l是生成的目标而出错。

    动态链接库

    前言

    动态链接库是程序运行时加载的库,当动态链接库正确安装后,所有的 程序都可以使用动态库来运行程序。动态链接库是目标文件的集合,目标文件在动态链接库中的组织方式是按照特殊方式形成的。库中函数和变量的地址是相对地址,不是绝对地址,其真实地址在调用动态库的程序加载时形成。

    动态链接库的名称有别名(soname)、真名(realname)和链接名(linker name):

    别名:libxxx.so,这种形式的库名正是执行编译命令时编译器要搜索的名字。

    真名:动态链接库的真实名称,一般总是在别名的基础上加上一个小版本号、发布版本等构成。

    链接名:程序链接时使用的库的名字。

    1. 生成动态链接库

      生成动态链接库的命令很简单,使用-fPIC选项或者-fpic选项。-fPIC和-fpic选项的作用是使得gcc生成的代码是位置无关的,例如下面的命令将print_hello.c编译生成动态链接库:

      img

      其中-shared选项告诉编译器生成一个动态链接库;-soname,libhello.so表示生成动态库的别名是libhello.so;-o libhello.so.1选项择表示生成名字为libhello.so.1的实际动态链接库文件。

      生成动态链接库之后一个很重要的问题就是安装,一般情况下将生成的动态链接库复制到系统默认的动态链接库的搜索路径下,通常有/lib,/usr/lib,/usr/local/lib,放到其中任何一个目录下都可以。

    2. 动态链接库的配置

      动态链接库并不是可以随意地使用,要在运行的 程序中使用动态链接库,需要指定系统的动态链接库搜索的路径,让系统找到运行所需要的动态链接库才可以。

      系统中的配置文件/etc/ld.so.conf是动态链接库的搜索路径配置文件。这这个文件内,存放着可被Linux共享的动态链接库所在目录的名字系统目录/lib,/usr/lib除外,这两个目录默认就是动态链接库的搜索路径),多个目录名间以空白字符(空格、换行等)或冒号或逗号分割。查看系统中的动态链接库配置文件的内容:

      img

      Linux的配置文件将目录ld.so.conf.d/中的配置文件包含了进来。

    3. 动态链接库管理命令

      为了让新增加的动态链接库能够被系统共享,需要运行动态链接库的 管理命令ldconfigldconfig命令的作用是在系统的默认搜索路径和动态链接库配置文件中所列出的目录里搜索动态链接库,创建动态链接库装入程序需要的链接和缓存文件。搜索完毕后,将结果写入缓存文件/etc/ld.so.cache中,文件中保存的是已经排好序的动态链接库名字列表。

      ldconfig命令的用法如下:

      img

      img

      img

    4. 使用动态链接库

    把当前工作目录(libhello.so.1所在的目录)加入动态链接库的搜索路径配置文件/etc/ld.so.conf中

    img

    执行ldconfig命令刷新缓存文件/etc/ld.so.cache:

    img

    我们会发现,执行ldconfig命令后,当前目录下生成了一个新的文件libhello.so(这是-soname,libhello.so选项导致的,如果没有该选项,那么ldconfig执行后不会生成新文件libhello.so。),使用ls命令会发现此文件是libhello.so.1的链接文件:

    img

    在编译程序时,使用动态链接库和静态链接库是一致的,使用"-lxxx"的方式:

    img

    编译后执行时出现如下错误:

    img

    其主要原因是使用了SELINUX。将其状态设置为disabled即可,方法如下:
    打开/etc/selinux/config,将selinux=enforcing或permissive改成disabled。然后存盘退出,重启系统。

    再次执行,结果如下:

    img

    运行时,还可能出现这样的错误:test: error while loading shared libraries: libxxx.so: cannot open shared object file: No such file or directory,这是由于程序运行时没有找到动态链接库造成的。程序编译时链接动态链接库和运行时使用动态链接库的概念是不同的,在运行时,程序链接的动态链接库需要在系统目录下才行。有几种办法可以解决此种问题:

    1. 将动态链接库的目录放到程序搜索路径中,可以将库的路径添加到环境变量LD_LIBRARY_PATH中实现:

    img

    1. 使用ld-Linux.so.2来加载程序,命令格式为:

    /lib/ ld-Linux.so.2 –library-path 路径 程序名

    加载test程序的命令为:

    img

    不过貌似并不是所有系统上都有ld-Linux.so.2。

    注意,如果系统的搜索路径下同时存在静态链接库和动态链接库,默认情况下会链接动态链接库。如果需要强制链接静态链接库,需要加上"-satic"选项。

    img

    动态加载库

    前言

    动态加载库和一般的动态链接库所不同的是,一般动态链接库在程序启动时就要寻找动态库,找到库函数;而动态加载库可以用程序的方法来控制什么时候加载。动态加载库主要有函数dlopen()、dlerror()、dlsym()和dlclose()来控制动态库的使用。函数原型如下:

    img

    1. 打开动态库dlopen()

      函数dlopen()按照用户指定的方式打开动态链接库,其中参数filename为动态链接库的文件名,flag为打开方式,一般为RTLD_LASY,函数的返回值为库的指针。

      例如,下面的代码使用dlopen打开当前目录下的动态库libhello.so:

      void *phandle = dlopen("./libhello.so", RTLD_LAZY);

      img

    2. 获得函数指针dlsym()

      使用动态链接库的目的是调用其中的函数,完成特定的功能。函数dlsym()可以获得动态链接库中指定函数的指针,然后可以使用这个函数指针进行操作。其中参数handle为dlopen()打开动态库后返回的句柄,参数symbol为函数的名称,返回值为函数指针。

    3. 使用动态加载库实例

      #include <stdio.h>

      #include <dlfcn.h>

      int

      main(void)

      {

      void (*printhello)(void);

      void *phandle = NULL;

      char *perr = NULL;

      phandle = dlopen("./libhello.so", RTLD_LAZY);

      if(!phandle)

      {

      ​ printf("Failed load library!\n");

      }

      perr = dlerror();

      if(perr != NULL)

      {

      ​ printf("%s\n", perr);

      ​ return(0);

      }

      printhello = dlsym(phandle, "print_hello");

      perr = dlerror();

      if(perr != NULL)

      {

      ​ printf("%s\n", perr);

      ​ return(0);

      }

      (*printhello)();

      dlclose(phandle);

      return(0);

      }

      编译运行:

      img

      注意,想要使用dlfcn.h中的函数,编译时必须加上选项-ldl

    小发现:

      有没有注意到,我们并没有使用用到include <hello.h>(假设我们为print_hello.c写了一个hello.h的头文件)。其实,只要编译命令中加入选项-lhello,hello.h头文件包含不包含都没有问题。为了验证这个问题,使用http://www.cnblogs.com/nufangrensheng/p/3518411.html中的程序清单11-1这个实例进行了测试:

      在http://www.cnblogs.com/nufangrensheng/p/3518411.html程序清单11-1编译过程中,曾遇到undefined reference to ‘pthread_create’这样的错误,原因在于没有在编译命令中加入-lpthread选项。

      如果我们加入了-lpthread选项,此时尝试着把程序中的#include <pthread.h>去掉,重新编译你会发现同样也是可以的。

      总结:如果使用的是标准库中的函数,则只需将头文件包含进来。如果使用的函数所在的库不是标准库(例如我们自己编写的库或标准以外扩展的库),则在编译时必须加入-lxxx选项,而头文件则可以包含也可以不包含,包含进来显得规范而已。

头文件和库文件的默认搜索路径

img

img

obj、LIB、DLL与EXE之间的关系

  1. OBJ是中间代码文件、LIB是静态库文件、DLL是动态库文件、EXE是可执行文件。
  2. 对于静态库文件,链接的时候把其中需要的东西抽取出来嵌入到EXE中,EXE较大。
  3. 对于动态库文件,EXE执行的时候依赖于DLL提供的功能,没有DLL则EXE无法执行,EXE较小。
  4. 一个C或CPP文件被编译后是一个OBJ,当所有必须要的C或CPP都被编译成OBJ后再统一链接成EXE
  5. LIB或DLL可以被看成是一堆OBJ的组合,发布后可以被链接入其它EXE或被其它EXE调用。
  6. LIB不可以调用其它LIB或DLL,DLL可以调用其它LIB或DLL。
  7. 动态库有LIB文件和DLL文件。LIB文件必须在编译期就被链接到应用程序中,而DLL在运行期才会被调用。如果有DLL文件,则对应的LIB文件一般是一些索引信息,具体的实现在DLL文件中。如果只有LIB文件,那么这个LIB文件是静态库文件,索引和实现都在其中。静态库文件有好处,给用户安装时就不需要再挂动态库了;但也有缺点,即导致EXE较大且失去了动态库的灵活性,在版本升级时,同时要发布新的EXE才行。
  8. 在动态库的情况下,有两个文件,一个是LIB文件、一个是DLL文件,LIB文件包含被DLL导出的函数的名称和位置,DLL包含实际的函数和数据,应用程序使用LIB文件链接到所需要使用的DLL文件,库中的函数和数据并不复制到可执行文件中,因此在EXE文件中存放的不是被调用的函数代码,而是DLL中所要调用的函数的内存地址,这样当一个或多个应用程序运行时再把程序代码和被调用的函数代码链接起来,从而节省了内存资源。
posted @ 2020-01-13 15:46  别再闹了  阅读(1981)  评论(0)    收藏  举报