关于Linux库(动态/静态库)、库文件、库函数,头文件、链接方式以及相关操作的知识汇总

1、头文件在哪里? 


头文件:作为一种包含功能函数、数据接口声明的载体文件,主要用于保存函数的声明。C++标准库头文件,不同的编译器默认路径不相同。

Vc6.0:一般在安装目录下的\VC98\INCLUDE目录, 
比如C:\Program Files (x86)\Microsoft Visual Studio\VC98\INCLUDE。 
Vs:一般在位于$VSPATH\VC\include路径下面。 
Gcc:一般默认在 /usr/include目录下。

2、库文件在哪里? 


库文件:存放函数的定义(实现),一般以二进制形式提供给用户使用; 
在 /usr/lib文件中。

库文件通过头文件向外导出接口,用户通过头文件找到库文件中需要的函数实现代码进行链接至程序当中。

3、可执行程序/命令在哪里? 


Linux下,可以通过which指令查看相关可执行文件的信息。 
/bin 存放二进制可执行命令 
/usr 目录下,几乎包括所有要用到的应用程序指令和文件

4、什么是库? 


库是一种可执行代码的二进制形式,是别人已经写好、适用成熟、在遵守相关协议下可以复用被操作系统载入内存中执行的代码。 
Linux中的库包括动态库和静态库。 


5、共享库/静态库区别? 


1)共享库(动态库):/lib 
动态链接下,一个程序被分成若干个文件(模块),包括可执行文件和程序所依赖的共享对象,所以动态链接过程需要考虑装载问题。 
其装载通过一系列由动态链接器提供的API(即四个函数:dlopen()打开动态库、dlsym()查找符号、dlerror()错误处理、dlclose()关闭动态库)进行操作。 
(1)Linux下的共享库:普通的ELF共享对象(ELF文件头包含整个文件的基本属性,主要用于API操作处理) 
(2)Windows下的共享库:DLL,动态链接即可实现运行加载

2)静态库: 
静态链接下,整个程序最终只会生成一个不可分割的可执行文件,即整个进程中只有一个文件要被映射。(空间与地址分配、符号解析与重定位)

3)产生动态链接方式的原因: 
静态链接,简而言之就是将程序运行所需的指令和数据全部装入内存中使得内存可以顺利运行,然而这种方式虽然简单但又粗暴,极大程度的浪费内存空间。针对程序运行时的局部性原理,我们可以将不常用的数据存放在磁盘中,只将不同的程序所需的不同模块装入内存,这样便有效的利用内存空间,即产生动态链接(覆盖装入、页映射)。 
动态链接的产生,则需要对应的动态库,由于动态库中的各模块可供用户选择使用从而也实现了模块资源共享,因此动态库又称共享库。 
4)二者区别: 
二者一般为编译集成的一系列接口,在程序源代码编译完成后通过编译器、链接器与这些库进行链接,最终实现程序的运行。 
区别主要在于库中代码被载入的时刻不同。 
(1)静态库:在程序编译时期会被连接到目标代码中,由于静态链接只会生成一个可执行代码,因此目标程序运行时不需要再载入。 
(2)共享库:在程序编译时期仅简单引用,不会被连接到目标代码中,而是在程序运行时才被载入,因此在程序运行时需要动态库存在。“以时间换取空间”

6、静态函数库和动态函数库的设计

函数库按照链接方式可分为:

  •  静态链接库
  •  动态链接库

Linux应用程序使用的主要函数库存放于/lib,和/user/lib目录下。其中动态链接库采用*.so形式命名,静态链接库使用*.a命名。

静态链接库的特点:程序所要用到的库函数代码在链接时全部被copy到程序中。从而导致的问题是,若多个进程在内存中同时运行,并且使用了相同的库函数将会产生多份拷贝,从而造成了对空间的浪费。

使用静态库编译的方法:因为,Linux下进行链接时默认的采用动态链接库。故直接用gcc -static XXX.c -o XXX.o静态库编译命令将会产生找不到静态库的错误。故我们需要首先安装静态库文件然后再进行静态编译。

如何编写自己的静态库?

  1. 编写自己的库函数C文件。假设命名为mylib.c
  2. 使用gcc -c mylib.c -o mylib.o命令生成可执行文件
  3. 使用ar cqs libmylib.a mylib.o  将可执行文件打包为*.a文件
  4. 将制作好的libmylib.a 文件复制到/usr/lib文件夹下

编译命令: gcc -static test.c -lmylib -o test.o 

如何编写自己的动态库?

  1. 编写自己的库函数C文件。假设命名为mylib.c
  2. 使用gcc -c mylib.c -o mylib.o命令生成可执行文件
  3. 使用gcc -shared -fpic mylib.o -o libmylib.so  将可执行文件打包为*.so文件
  4. 将制作好的libmylib.so 文件复制到/usr/lib文件夹下

编译命令: gcc  test.c -lmylib -o test.o 

注:

  1. 使用自己制作的静态库文件时要编写相对应的头文件.
  2. -lname :gcc 在链接时,默认只会链接C函数库,而对于其他的函数库需要使用 -l 选项来显示地指明需要链接。如本例中的 -lmylib   虽然,mylib库在/usr/lib 文件夹下名字为libmylib.a 。但是编译命令为-lmylib即可。
  3. -fpic 使输出的对象模块是按照重定位地址方式生成的

    4.-shared 指明生成动态链接库

附录:

  1. 库文件mylib.c
#include<stdio.h>
int compare_num(int a, int b)
{
     if(a>b)
          printf("a>b\n");
     else
          printf("a<=b\n");
     return 0;
}

     2. 头文件 mylib.h

int compare_num(int a, int b);

     3. 测试文件 test.c

#include<stdio.h>
#include"mylib.h"
int main()
{
     compare_num(1, 5);
     return 0;
}

7、linux+C环境下动态管理头文件和库文件

1.一个Makefile的例:

-L是指定链接时搜索路径,比如-L./等;

-l是指定具体的库文件,比如-lhello,是指定./目录下的libhello.so或libhello.a 文件

-I或-include是指定头文件目录

-Wl,-rpath=./:../是指定运行时搜索路径

# 指令编译器和选项  
CC = gcc  
CFLAGS = -Wall -std=gnu99  
  
# 目标文件  
TARGET = test  
# C文件  
SRCS = test.c  
# 头文件查找路径  
INC = -I. -I../
# 库文件和库查找路径  
DLIBS = -ltest  
LDFLAGS = -L./lib  -L.
# 指定运行时的库文件路径  
RPATH = -Wl,-rpath=./lib:../  
  
# 目标文件  
OBJS = $(SRCS:.c=.o)  
  
# 链接为可执行文件  
$(TARGET):$(OBJS)  
[tab]$(CC) -o $@ $^ $(LDFLAGS) $(DLIBS) $(RPATH)  
  
clean:  
[tab]rm -rf $(TARGET) $(OBJS)  
  
# 连续动作,请清除再编译链接,最后执行  
exec:clean $(TARGET)  
[tab]@echo 开始执行  
[tab]./$(TARGET)  
[tab]@echo 执行结束  
  
# 编译规则 $@代表目标文件 $< 代表第一个依赖文件  
%.o:%.c  
[tab]$(CC) $(CFLAGS) $(INC) -o $@ -c $<  

    【1】DLIBS = -ltest 指定共享库,请注意共享库的名称为libtest.so,而-l参数只取test部分,去掉前缀lib和后缀.so。

    【2】LDFLAGS = -L./lib -L./ 指定共享库路径,请注意上一步中已经把共享库复制到lib目录中。

    【3】INC = -I./lib -I../  指定libtest.h目录,也可把libtest.h复制到test.c所在的目录。

    【4】$(CC) -o $@ $^ $(LDFLAGS) $(DLIBS) 链接过程指定共享库查找路径,指定共享库名称。

    【5】第【1】和第【2】点只在链接过程有效,在执行过程中需要通过-Wl,-rpath=<path>指定共享库路径。

      [6]  gcc -c -fPIC test.c -o test.o
        gcc -shared -fPIC -o libtest.so test.o

ar rc libhello.a hello.o

       该命令将hello.o添加到静态库文件libhello.a,ar命令就是用来创建、修改库的,也可以从库中提出单个模块,参数r表示在库中插入或者替换模块,c表示创建一个库

8、用ldd命令查看程序的依赖库关系

   基本上每一个linux 程序都至少会有一个动态库,查看某个程序使用了那些动态库,使用ldd命令查看 

# ldd /bin/ls
linux-vdso.so.1 => (0x00007fff597ff000)
libselinux.so.1 => /lib64/libselinux.so.1 (0x00000036c2e00000)
librt.so.1 => /lib64/librt.so.1 (0x00000036c2200000)
libcap.so.2 => /lib64/libcap.so.2 (0x00000036c4a00000)
libacl.so.1 => /lib64/libacl.so.1 (0x00000036d0600000)
libc.so.6 => /lib64/libc.so.6 (0x00000036c1200000)
libdl.so.2 => /lib64/libdl.so.2 (0x00000036c1600000)
/lib64/ld-linux-x86-64.so.2 (0x00000036c0e00000)
libpthread.so.0 => /lib64/libpthread.so.0 (0x00000036c1a00000)
libattr.so.1 => /lib64/libattr.so.1 (0x00000036cf600000)

   这么多so,是的。使用ldd显示的so,并不是所有so都是需要使用的,下面举个例子

main.cpp

#include <stdio.h>
#include <iostream>
#include <string>
using namespace std;

int main ()
{
   cout << "test" << endl;
   return 0;
}

   使用缺省参数编译结果

# g++ -o demo main.cpp
# ldd demo
    linux-vdso.so.1 => (0x00007fffcd1ff000)
        libstdc++.so.6 => /usr/lib64/libstdc++.so.6 (0x00007f4d02f69000)
        libm.so.6 => /lib64/libm.so.6 (0x00000036c1e00000)
        libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00000036c7e00000)
        libc.so.6 => /lib64/libc.so.6 (0x00000036c1200000)
        /lib64/ld-linux-x86-64.so.2 (0x00000036c0e00000)

   如果我链接一些so,但是程序并不用到这些so,又是什么情况呢,下面我加入链接压缩库,数学库,线程库

# g++ -o demo -lz -lm -lrt main.cpp
# ldd demo
        linux-vdso.so.1 => (0x00007fff0f7fc000)
        libz.so.1 => /lib64/libz.so.1 (0x00000036c2600000)
        librt.so.1 => /lib64/librt.so.1 (0x00000036c2200000)
        libstdc++.so.6 => /usr/lib64/libstdc++.so.6 (0x00007ff6ab70d000)
        libm.so.6 => /lib64/libm.so.6 (0x00000036c1e00000)
        libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00000036c7e00000)
        libc.so.6 => /lib64/libc.so.6 (0x00000036c1200000)
        libpthread.so.0 => /lib64/libpthread.so.0 (0x00000036c1a00000)
        /lib64/ld-linux-x86-64.so.2 (0x00000036c0e00000)

  看看,虽然没有用到,但是一样有链接进来,那看看程序启动时候有没有去加载它们呢

# strace ./demo
    execve("./demo", ["./demo"], [/* 30 vars */]) = 0
    ... = 0
    open("/lib64/libz.so.1", O_RDONLY) = 3
    ...
    close(3) = 0
    open("/lib64/librt.so.1", O_RDONLY) = 3
    ...
    close(3) = 0
    open("/usr/lib64/libstdc++.so.6", O_RDONLY) = 3
    ...
    close(3) = 0
    open("/lib64/libm.so.6", O_RDONLY) = 3
    ...
    close(3) = 0
    open("/lib64/libgcc_s.so.1", O_RDONLY) = 3
    ...
    close(3) = 0
    open("/lib64/libc.so.6", O_RDONLY) = 3
    ...
    close(3) = 0
    open("/lib64/libpthread.so.0", O_RDONLY) = 3
    ...
    close(3) = 0
    ...

  看,有加载,所以必定会影响进程启动速度,所以我们最后不要把无用的so编译进来,这里会有什么影响呢?

   大家知不知道linux从程序(program或对象)变成进程(process或进程),要经过哪些步骤呢,这里如果详细的说,估计要另开一篇文章。简单的说分三步:

    1、fork进程,在内核创建进程相关内核项,加载进程可执行文件;

    2、查找依赖的so,一一加载映射虚拟地址

    3、初始化程序变量。

  可以看到,第二步中dll依赖越多,进程启动越慢,并且发布程序的时候,这些链接但没有使用的so,同样要一起跟着发布,否则进程启动时候,会失败,找不到对应的so。所以我们不能像上面那样,把一些毫无意义的so链接进来,浪费资源。但是开发人员写makefile 一般有没有那么细心,图省事方便,那么有什么好的办法呢。继续看下去,下面会给你解决方法。

  先使用 ldd -u demo 查看不需要链接的so,看下面,一面了然,无用的so全部暴露出来了吧

# ldd -u demo
Unused direct dependencies:
        /lib64/libz.so.1
        /lib64/librt.so.1
        /lib64/libm.so.6
        /lib64/libgcc_s.so.1
  使用 -Wl,--as-needed 编译选项

# g++ -Wl,--as-needed -o demo -lz -lm -lrt main.cpp
# ldd demo
        linux-vdso.so.1 => (0x00007fffebfff000)
        libstdc++.so.6 => /usr/lib64/libstdc++.so.6 (0x00007ff665c05000)
        libc.so.6 => /lib64/libc.so.6 (0x00000036c1200000)
        libm.so.6 => /lib64/libm.so.6 (0x00000036c1e00000)
        /lib64/ld-linux-x86-64.so.2 (0x00000036c0e00000)
        libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00000036c7e00000)
# ldd -u demo
Unused direct dependencies:

  呵呵,办法很简单省事吧,本文主要讲so依赖的一些问题,下一篇将介绍so的路径方面一些不为人知的小秘密

我们知道linux链接so有两种途径:显示和隐式。所谓显示就是程序主动调用dlopen打开相关so;这里需要补充的是,如果使用显示链接,上篇文章讨论的那些问题都不存在。首先,dlopen的so使用ldd是查看不到的。其次,使用dlopen打开的so并不是在进程启动时候加载映射的,而是当进程运行到调用dlopen代码地方才加载该so,也就是说,如果每个进程显示链接a.so;但是如果发布该程序时候忘记附带发布该a.so,程序仍然能够正常启动,甚至如果运行逻辑没有触发运行到调用dlopen函数代码地方。该程序还能正常运行,即使没有a.so.

9、Linux 中如何快速查看 C 库函数的头文件以及相应的函数信息?


使用man帮助或grep

1)man命令 函数名,则能够打印该函数的所有说明,当然这个函数必须是Linux-C本身就有的函数。

2)grep "keyword" /usr/include/*.h |grep "typedef"

以上方法可以查找关键字为keyword的结构体、类型、函数原型的定义,typedef可以替换为define尝试查找,几次重复查找找到的定义,就能最终确定。

如:grep “time_t” /usr/include/*.h |grep “typedef”可以查找到“typedef_time_t time_t;”

Locatekeyword.h 可以查找名为keyword 的头文件所在地目录树。

10、Linux下如何查找一个函数在哪个库中

有一些常识或者说平时的积累是必要的,终究常用的库很少。
当我遇到一个函数不知道所属的库的时候,我会先找到头文件所属的包,然后看看那个包(或者相关包)的.a或者.so文件有哪些,然后:
objdump -x 库文件 | grep 函数名
确认函数名是定义而不是引用以后,就知道需要链接这个库了。

比如:查找htons对应的库文件

1)、man htons,里面讲的是arpa/inet.h;
2)、假设你用的是rpm类型的系统。

2.1)、centos/redhat下查看某个文件或命令属于哪个rpm包:

$ yum provides /etc/passwd
或者
$ rpm -qf /etc/passwd
2.2)、ubuntu及衍生版:

sudo dpkg -S whereis或
sudo dpkg-query -S /usr/bin/whereis
不过该命令不如rpm -qf强大,如/etc/passwd就查不出。

2.3)、gentoo及衍生版

#未安装的文件
e-file qmake
#已安装的文件查看
equery b <filename>
或qfile <filename>
如果通过上面还是搜索不到对应的包文件,那就baidu或google搜索了一下。
3)、貌似这个glibc-headers只是个包含头文件的软件包,看看其他类似的软件包是什么:rpm -qa | grep glibc
4)、我看见了glibc,glibc-devel,glibc-common,觉得glibc-devel肯定是提供.a(静态链接)的
5)、rpm -q --filesbypkg glibc-devel| grep \.a$ | awk '{print $2}' |xargs grep htons找到所有带htons的静态库文件
6)、只有一个:/usr/lib/libc.a
7)、如果有好几个的话,用objdump -x看看符号表

11、查看Linux下*.a库文件中文件、函数、变量等情况


在Linux 下经常需要链接一些 *.a的库文件,那怎么查看这些*.a 中包含哪些文件、函数、变量:

        1. 查看文件:ar -t*.a

        2. 查看函数、变量:nm*.a

发布了56 篇原创文章 · 获赞 37 · 访问量 11万+

猜你喜欢

转载自blog.csdn.net/weixin_42096901/article/details/102627357