• 【Linux】基础IO,软硬链接,动静态库


    1. 认识IO

    什么是IO

    I/O简单来说对应的就是两个单词Input和Output,指的是计算机系统与外部环境(通常是硬件设备或其他计算机系统)之间的数据交换过程

    I/O 可以分为两种主要类型:

    1. 输入(Input): 输入是指计算机系统接收来自外部环境的数据或信息的过程。例如,当用户在键盘上输入文本时,这些输入字符被视为输入操作。输入可以来自各种外部源,包括键盘、鼠标、传感器、网络连接、磁盘驱动器等。计算机系统需要能够接收和处理这些输入数据,以便执行相应的操作。
    2. 输出(Output): 输出是指计算机系统将处理后的数据或信息发送到外部环境的过程。例如,当计算机系统将文本显示在屏幕上、打印文档到打印机或将数据写入磁盘驱动器时,这些都是输出操作。输出使计算机系统能够与用户或其他设备进行有效的通信。

    在计算机程序中,I/O 操作通常是相对较慢的操作,因为涉及到与外部设备通信或存储介质的读写,而这些操作比内存中的数据处理更为耗时。因此,在编写高效的程序时,需要注意如何最小化 I/O 操作的次数,以提高性能。此外,对于某些应用程序,如数据库管理系统和网络通信应用程序,有效的 I/O 管理对于系统的响应时间和吞吐量至关重要。

    2.C文件接口的IO

    写文件

    #include 
    #include 
    int main()
    {
        FILE* fp=fopen("myfile","w");
        if(fp==NULL){
            perror("open");
            return 1;
        }
    
        const char* str="hello world\n";
        int cnt=5;
        while(cnt--)
            fwrite(str,strlen(str),1,fp);
        
        fclose(fp);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    image-20230909160750952

    读文件

    #include 
    #include 
    int main()
    {
        FILE* fp=fopen("myfile","r");
        if(fp==NULL){
            perror("open");
            return 1;
        }
    
        const char* msg="hello world\n";
        char* str[1024];
        while (1)
        {
            size_t s=fread(str,1,strlen(msg),fp);
            if(s>0)
            {
                str[s]=0;
                printf("%s",str);
            }
            if(feof(fp)) break;
        }
        
        fclose(fp);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    image-20230909161531953

    3. 系统文件接口的I/O

    操作文件,除了上述C接口(当然其他语言也有),我们还可以采用系统接口来进行文件访问

    接口介绍

    #include 
    #include 
    #include 
    int open(const char *pathname, int flags);
    int open(const char *pathname, int flags, mode_t mode);
    pathname: 要打开或创建的目标文件
    flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。
    参数:
        O_RDONLY: 只读打开
        O_WRONLY: 只写打开
        O_RDWR : 读,写打开
        这三个常量,必须指定一个且只能指定一个
        O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
        O_APPEND: 追加写
    返回值:
        成功:新打开的文件描述符
        失败:-1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    mode_t理解:直接看man 手册,比什么都清楚。
    open 函数具体使用哪个,和具体应用场景相关,如目标文件不存在,需要open创建,则第三个参数表示创建文件的默认权限,否则,使用两个参数的open。

    写文件

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    int main(){
        int fd=open("myfile",O_WRONLY);
        if(fd<0){
            perror("open");
        }
    
        const char* msg="hello world!!\n";
        int cnt=5;
        while (cnt--){
            //fd: 后面讲, msg:缓冲区首地址, len: 本次读取,期望写入多少个字节的数据。 返回值:实际写了多少字节数据
            write(fd,msg,strlen(msg));
        }
        
        close(fd);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    读文件

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    int main()
    {
        int fd=open("myfile",O_RDONLY);
        if(fd<0){
            perror("open");
        }
    
        const char* msg="hello world!!\n";
        char* str[1024];
        while (read(fd,str,strlen(msg)))
        {
            printf("%s",str);
        }
        
        close(fd);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    open函数返回值

    在认识返回值之前,先来认识一下两个概念: 系统调用和 库函数

    上面的 fopen fclose fread fwrite 都是C标准库当中的函数,我们称之为库函数(libc)。
    而, open close read write lseek 都属于系统提供的接口,称之为系统调用接口

    image-20230909163535105
    所以,可以认为,f#系列的函数,都是对系统调用的封装,方便二次开发。

    文件描述符fd

    这个文件描述符fd究竟是个什么数字呢?

    我们可以试着把它打印出来看看

    #include 
    #include 
    #include 
    #include 
    #include 
    int main()
    {
        int fd=open("myfile",O_RDONLY);
        if(fd<0){
            perror("open");
        }
        printf("%d\n",fd);
        close(fd);
        return 0;
    }
    //输出结果
    //3
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    文件描述符就是一个小整数,但它为什么是3呢?学过C语言的应该都知道

    C默认会打开三个输入输出流,分别是stdin, stdout, stderr仔细观察发现,这三个流的类型都是FILE*, fopen返回值类型,文件指针,其实这三个流就对应数字0,1,2,进而可以得出,fd本质上就是数组的下标,只不过这个数组是系统帮我们维护的

    image-20230910122140248

    而现在知道,文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件

    Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2

    0,1,2对应的物理设备一般是:键盘,显示器,显示器、

    所以输入输出还可以采用如下方式:

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    int main()
    {
        char buffer[1024];
        ssize_t s=read(0,buffer,sizeof buffer);
        if(s>0){
            buffer[s]=0;
            write(1,buffer,strlen(buffer));
            write(2,buffer,strlen(buffer));
        }
        return 0;
    }
    //运行结果
    //输入:hello world!!!
    //输出:
    //hello world!!!
    //hello world!!!
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    文件描述符的分配规则

    上面的例子中,我们新打开一个文件输出后发现是 fd: 3

    现在关闭文件描述符0或者2再来看看

    #include 
    #include 
    #include 
    #include 
    int main()
    {
        close(0);
        //close(2);
        int fd = open("myfile", O_RDONLY);
        if(fd < 0){
            perror("open");
            return 1;
        }
        printf("fd: %d\n", fd);
        close(fd);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    发现是结果是: fd: 0 或者 fd 2 可见,文件描述符的分配规则:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。

    那如果我们关闭标准输出1呢?

    #include 
    #include 
    #include 
    #include 
    #include 
    int main()
    {
        close(1);
        int fd = open("myfile", O_WRONLY|O_CREAT, 00644);
        if(fd < 0){
            perror("open");
            return 1;
        }
        printf("fd: %d\n", fd);
        close(fd);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    image-20230910123418750

    此时,我们发现,本来应该输出到显示器上的内容,输出到了文件 myfile 当中,其中,fd=1。这种现象叫做输出重定向。常见的重定向有:>, >>, <

    //比如我想让myfile文件写入ls -l 打印的内容
    [hdm@centos7 BasicIO]$ ls -l > myfile 
    [hdm@centos7 BasicIO]$ cat myfile 
    total 28
    -rw-r--r-- 1 hdm wheel    65 Sep  9 15:56 makefile
    -rw-r--r-- 1 hdm wheel     0 Sep 10 12:40 myfile
    -rwxr-xr-x 1 hdm wheel 16576 Sep 10 12:33 test
    -rw-r--r-- 1 hdm wheel   652 Sep 10 12:33 test.c
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    重定向

    重定向的本质是什么呢?

    image-20230910125105172

    不过我们要实现重定向,并不会像上面的列子一样,先把一个文件描述符关闭再打开,系统给了我们一套接口

    dup2 系统调用

    函数原型如下:

    #include 
    int dup2(int oldfd, int newfd);
    
    • 1
    • 2
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    int main()
    {
        int fd=open("myfile",O_WRONLY | O_CREAT,0644);
        if(fd<0){
            perror("open");
        }
        dup2(fd,1);
        printf("fd: %d\n", fd);
        fflush(stdout);
        const char* str="hello\n";
        const char* str1="nihao\n";
    
        fprintf(stdout,"%s",str1);
        write(1,str,strlen(str));
        write(fd,str,strlen(str));
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    image-20230910130802335

    printf是C库当中的IO函数,一般往 stdout 中输出,但是stdout底层访问文件的时候,找的还是fd:1, 但此时,fd:1下标所表示内容,已经变成了myfile的地址,不再是显示器文件的地址,所以,输出的任何消息都会往文件中写入,进而完成输出重定向。

    FILE

    因为IO相关函数与系统调用接口对应,并且库函数封装系统调用,所以本质上,访问文件都是通过fd访问的。所以C库当中的FILE结构体内部,必定封装了fd。

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    int main()
    {
        const char* str1="hello printf\n";
        const char* str2="hello fprintf\n";
        const char* str3="hello write\n";
    
        printf("%s",str1);
        fprintf(stdout,"%s",str2);
        write(1,str3,strlen(str3));
    
        fork();
        return 0;
    }
    //运行出结果:
    //hello printf
    //hello fprintf
    //hello write
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    但如果对进程实现输出重定向呢? ./test > myfile , 我们发现结果变成了:

    [hdm@centos7 BasicIO]$ ./test  >myfile 
    [hdm@centos7 BasicIO]$ cat myfile 
    hello write
    hello printf
    hello fprintf
    hello printf
    hello fprintf
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    我们发现 printf 和 fwrite (库函数)都输出了2次,而 write 只输出了一次(系统调用)。

    为什么呢?肯定和fork有关!

    • 一般C库函数写入文件时是全缓冲的,而写入显示器是行缓冲。

    • printf fwrite 库函数会自带缓冲区,当发生重定向到普通文件时,数据的缓冲方式由行缓冲变成了全缓冲。

    • 而我们放在缓冲区中的数据,就不会被立即刷新,甚至fork之后

    • 但是进程退出之后,会统一刷新,写入文件当中。

    • 但是fork的时候,父子数据会发生写时拷贝,所以当你父进程准备刷新的时候,子进程也就有了同样的一份数据,随即产生两份数据。

    • write 没有变化,说明没有所谓的缓冲。

    综上: printf fwrite 库函数会自带缓冲区,而 write 系统调用没有带缓冲区。另外,我们这里所说的缓冲区,都是用户级缓冲区。其实为了提升整机性能,OS也会提供相关内核级缓冲区,不过不再我们讨论范围内。
    那这个缓冲区谁提供呢? printf fwrite 是库函数, write 是系统调用,库函数在系统调用的“上层”, 是对系统调用的“封装”,但是 write 没有缓冲区,而 printf fwrite 有,足以说明,该缓冲区是二次加上的,又因为是C,所以由C标准库提供。

    4. 理解文件系统

    我们使用ls -l的时候看到的除了看到文件名,还看到了文件的其他数据。

    [hdm@centos7 BasicIO]$ ls -l
    total 12
    -rw-r--r-- 1 hdm wheel   65 Sep 10 12:57 makefile
    -rw-r--r-- 1 hdm wheel   66 Sep 10 15:50 myfile
    -rw-r--r-- 1 hdm wheel 1101 Sep 10 15:48 test.c
    
    • 1
    • 2
    • 3
    • 4
    • 5

    每行包含7列:

    • 模式

    • 硬链接数

    • 文件所有者

    • 大小

    • 最后修改时间

    • 文件名

    image-20230910160312460

    其实这个信息除了通过这种方式来读取,还有一个stat命令能够看到更多信息

    [hdm@centos7 BasicIO]$ stat test.c 
     File: ‘test.c’
     Size: 1101            Blocks: 8          IO Block: 4096   regular file
    Device: fd01h/64769d    Inode: 1582421     Links: 1
    Access: (0644/-rw-r--r--)  Uid: ( 1000/     hdm)   Gid: (   10/   wheel)
    Access: 2023-09-10 15:48:26.249722588 +0800
    Modify: 2023-09-10 15:48:26.086715074 +0800
    Change: 2023-09-10 15:48:26.086715074 +0800
    Birth: -
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    上面的执行结果有几个信息需要解释清楚

    inode

    为了能解释清楚inode我们先简单了解一下文件系统

    image-20230910165605740

    Linux ext2文件系统,上图为磁盘文件系统图(内核内存映像肯定有所不同),磁盘是典型的块设备,硬盘分区被划分为一个个的block。一个block的大小是由格式化的时候确定的,并且不可以更改。

    • Block Group:ext2文件系统会根据分区的大小划分为数个Block Group。而每个Block Group都有着相同的结构组成。政府管理各区的例子
    • 超级块(Super Block):存放文件系统本身的结构信息。记录的信息主要有:bolck 和 inode的总量,未使用的block和inode的数量,一个block和inode的大小,最近一次挂载的时间,最近一次写入数据的时间,最近一次检验磁盘的时间等其他文件系统的相关信息。Super Block的信息被破坏,可以说整个文件系统结构就被破坏了
    • GDT,Group Descriptor Table:块组描述符,描述块组属性信息
    • 块位图(Block Bitmap):Block Bitmap中记录着Data Block中哪个数据块已经被占用,哪个数据块没有被占用
    • inode位图(inode Bitmap):每个bit表示一个inode是否空闲可用
    • i节点表:存放文件属性 如 文件大小,所有者,最近修改时间等
    • 数据区:存放文件内容

    将属性和数据分开存放的想法看起来很简单,但实际上是如何工作的呢?我们通过touch一个新文件来看看如何工作

    [hdm@centos7 BasicIO]$ touch myfile
    [hdm@centos7 BasicIO]$ ls -i myfile 
    1574424 myfile
    
    • 1
    • 2
    • 3

    为了说明问题,我们将上图简化

    image-20230910170312651

    创建一个新文件主要有一下4个操作:

    1. 存储属性
      内核先找到一个空闲的i节点(这里是1574424)。内核把文件信息记录到其中。

    2. 存储数据
      该文件需要存储在三个磁盘块,内核找到了三个空闲块:300,500,800。将内核缓冲区的第一块数据复制到300,下一块复制到500,以此类推。

    3. 记录分配情况
      文件内容按顺序300,500,800存放。内核在inode上的磁盘分布区记录了上述块列表。

    4. 添加文件名到目录

    新的文件名myfile。linux如何在当前的目录中记录这个文件?内核将入口(1574424,myfile)添加到目录文件。文件名和inode之间的对应关系将文件名和文件的内容及属性连接起来。

    理解软硬链接

    我们看到,真正找到磁盘上文件的并不是文件名,而是inode。 其实在linux中可以让多个文件名对应于同一个inode。

    //ln 属于硬链接的文件 硬链接文件的名字
    ln file file.link
    
    • 1
    • 2
    [hdm@centos7 BasicIO]$ touch myfile
    [hdm@centos7 BasicIO]$ ls -li 
    1574424 -rw-r--r-- 1 hdm wheel   66 Sep 11 13:27 myfile
    hdm@centos7 BasicIO]$ ln myfile myfile.link 
    [hdm@centos7 BasicIO]$ ls -li 
    1574424 -rw-r--r-- 2 hdm wheel   66 Sep 11 13:27 myfile
    1574424 -rw-r--r-- 2 hdm wheel   66 Sep 11 13:27 myfile.link
    [hdm@centos7 BasicIO]$ unlink myfile.link 
    [hdm@centos7 BasicIO]$ ls -li
    1574424 -rw-r--r-- 1 hdm wheel   66 Sep 11 13:27 myfile
    [hdm@centos7 BasicIO]$ rm myfile 
    [hdm@centos7 BasicIO]$ ls -li
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    myfile和myfile.link的链接状态完全相同,他们被称为指向文件的硬链接。内核记录了这个连接数,inode
    1574424的硬连接数为2。
    我们在删除文件时干了两件事情:

    1.在目录中将对应的记录删除,

    2.将硬连接数-1,如果为0,则将对应的磁盘释放。

    image-20230911134946032

    为什么创建目录之后,它的当前目录硬链接数会加一?

    因为在当前目录比如test下创建一个目录比如a,那么进入这个目录a之后,里面会存在两个隐藏目录, 一个. ..对应的含义分别是当前目录和上级,而这个 ..就是对上级目录test的硬链接。而且这样对目录的硬链接我们普通用户自己不能进行操作,只能由操作系统分配

    为什么我们自己不能对目录进行硬链接?

    1. 循环链接问题: 允许目录硬链接可能导致循环链接的问题。当一个目录被硬连接到另一个目录时,可以创建一个链接链,其中一个目录包含硬连接到另一个目录,而另一个目录包含硬连接到第一个目录,从而形成一个无限循环的链接链。这种循环链接对文件系统的稳定性和可用性造成了严重威胁,因为它会导致文件系统陷入无限循环的操作中。
    2. 文件系统的一致性和安全性: 目录是用于组织文件和子目录的结构,包括文件和子目录的层次结构和元数据(如权限、所有者等)。如果允许硬链接目录,可能会导致混淆和权限问题。例如,不同的用户或进程可以在不同位置创建硬链接,这可能导致混淆和权限冲突。目录硬链接可能会对文件系统的一致性和安全性造成严重影响。
    3. 性能问题: 如果目录可以硬连接,那么在处理目录结构时,文件系统必须处理硬链接的情况,这可能导致性能问题和更复杂的目录操作实现。

    软链接
    硬链接是通过inode引用另外一个文件,软链接是通过名字引用另外一个文件,在shell中的做法

    软连接其实就跟windows系统上的文件快捷方式一样,只要被链接那个文件删除了,那么软连接也将失效

    //ln 属于软链接的文件 软链接文件的名字
    ln -s file file.link
    
    • 1
    • 2
    [hdm@centos7 BasicIO]$ touch myfile
    [hdm@centos7 BasicIO]$ ls -li
    1574424 -rw-r--r-- 1 hdm wheel    0 Sep 11 13:34 myfile
    [hdm@centos7 BasicIO]$ ln -s myfile myfile.link.s
    [hdm@centos7 BasicIO]$ ls -li
    1574424 -rw-r--r-- 1 hdm wheel    0 Sep 11 13:34 myfile
    1574967 lrwxrwxrwx 1 hdm wheel    6 Sep 11 13:34 myfile.link.s -> myfile
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    image-20230911133934619

    我们在用 stat命令进行查看文件属性的时候,会出现三行时间,那它们分别代表上面含义呢?

    image-20230911135415361

    • Access 最后访问时间

      访问时间: 访问时间是文件或目录上一次被访问的时间。每当文件或目录被打开、读取或执行时,访问时间都会被更新。访问时间的记录有助于了解文件或目录的访问频率,但在某些情况下,频繁更新访问时间可能会对性能产生影响。可以使用 statls -lu 命令查看文件的访问时间。

    • Modify 文件内容最后修改时间

      访问时间: 访问时间是文件或目录上一次被访问的时间。每当文件或目录被打开、读取或执行时,访问时间都会被更新。访问时间的记录有助于了解文件或目录的访问频率,但在某些情况下,频繁更新访问时间可能会对性能产生影响。可以使用 statls -lu 命令查看文件的访问时间。

    • Change 属性最后修改时间

      更改时间: 更改时间是文件或目录上一次元数据(metadata)发生更改的时间。元数据包括文件的权限、所有者、链接数等信息。当文件的元数据发生更改时(例如,文件的权限被修改),更改时间将被更新。更改时间还包括文件或目录的创建时间。可以使用 stat 命令查看文件的更改时间。

    5. 动态库和静态库

    动态库和静态库是在编程中常用的两种库文件类型,它们有不同的特点和用途。

    1. 静态库(Static Library):
      • 静态库是一组已编译的对象文件的集合,通常以.a(在Unix/Linux)或.lib(在Windows)为文件扩展名。
      • 当你将静态库链接到一个可执行文件中时,编译器会将库中的代码和数据复制到最终的可执行文件中。
      • 静态库使得可执行文件具有所有必需的代码,因此它们通常较大。
      • 静态库的优点是独立性,不需要依赖外部库文件,但可能导致可执行文件变得较大。
    2. 动态库(Dynamic Link Library, DLL,或Shared Library):
      • 动态库也是一组已编译的对象文件的集合,通常以.dll(在Windows)或.so(在Unix/Linux)为文件扩展名。
      • 与静态库不同,动态库的代码和数据不会被复制到可执行文件中。相反,可执行文件包含指向库的引用,库在运行时加载到内存中。
      • 动态库使得多个程序可以共享同一份库,节省了磁盘和内存空间。
      • 动态库的缺点是,如果库文件不存在或版本不匹配,程序可能无法运行。

    选择使用动态库还是静态库取决于项目的需求和设计。通常情况下:

    • 使用动态库可以减小可执行文件的大小,因为多个程序可以共享同一个库。
    • 使用静态库可以确保可执行文件的独立性,不受外部库的影响,但可能导致可执行文件变得较大。

    生成静态库

    我们可以自己制定一个动静态库来测试,例如

    /add.h/
    #ifndef __ADD_H__
    #define __ADD_H__
    int add(int a, int b);
    #endif // __ADD_H__
    /add.c/
    #include "add.h"
    int add(int a, int b){
        return a + b;
    }
    /sub.h/
    #ifndef __SUB_H__
    #define __SUB_H__
    int sub(int a, int b);
    #endif // __SUB_H__
    /add.c/
    #include "add.h"
    int sub(int a, int b){
        return a - b;
    }
    ///main.c
    #include 
    #include "add.h"
    #include "sub.h"
    int main(void)
    {
        int a = 10;
        int b = 20;
        printf("add(%d,%d)=%d\n", a, b, add(a, b));
        a = 100;
        b = 200;
        printf("sub(%d,%d)=%d\n", a, b, sub(a, b));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    [hdm@centos7 myfile]$ ls
    add.c  add.h  main.c  sub.c  sub.h
    [hdm@centos7 myfile]$ gcc -c add.c -o add.o 
    [hdm@centos7 myfile]$ gcc -c sub.c -o sub.o
    
    生成静态库
    [hdm@centos7 myfile]$ ar -rc libmymath.a add.o sub.o
    ar是gnu归档工具,rc表示(replace and create)
    
    查看静态库中的目录列表
    [hdm@centos7 myfile]$ ar -tv libmymath.a 
    rw-r--r-- 1000/10   1240 Sep 11 15:49 2023 add.o
    rw-r--r-- 1000/10   1232 Sep 11 15:50 2023 sub.o
    t:列出静态库中的文件
    v:verbose 详细信息
    
    [hdm@centos7 myfile]$ gcc main.c -L. -lmymath
    -L 指定库路径
    -l 指定库名
    测试目标文件生成后,静态库删掉,程序照样可以运行。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    库搜索路径

    • 从左到右搜索-L指定的目录。

    • 由环境变量指定的目录 (LIBRARY_PATH)

    • 由系统指定的目录
      /usr/lib
      /usr/local/lib

    生成动态库

    shared: 表示生成共享库格式
    fPIC:产生位置无关码(position independent code)
    库名规则:libxxx.so

    [hdm@centos7 myfile]$ ls
    add.c  add.h  add.o  libmymath.a  main.c  sub.c  sub.h  sub.o
    [hdm@centos7 myfile]$  gcc -fPIC -c sub.c add.c
    [hdm@centos7 myfile]$ gcc -shared -o libmymath.so *.o
    [hdm@centos7 myfile]$ ls -l
    total 44
    -rw-r--r-- 1 hdm wheel    57 Sep 11 15:42 add.c
    -rw-r--r-- 1 hdm wheel     0 Sep 11 15:36 add.h
    -rw-r--r-- 1 hdm wheel  1240 Sep 11 15:57 add.o
    -rw-r--r-- 1 hdm wheel  2680 Sep 11 15:50 libmymath.a
    -rwxr-xr-x 1 hdm wheel 15824 Sep 11 15:58 libmymath.so
    -rw-r--r-- 1 hdm wheel   236 Sep 11 15:52 main.c
    -rw-r--r-- 1 hdm wheel    57 Sep 11 15:43 sub.c
    -rw-r--r-- 1 hdm wheel    66 Sep 11 15:42 sub.h
    -rw-r--r-- 1 hdm wheel  1232 Sep 11 15:57 sub.o
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    使用动态库

    编译选项
    l:链接动态库,只要库名即可(去掉lib以及版本号)
    L:链接库所在的路径.

    示例: gcc main.c -lmymath -L.

    运行动态库
    1、拷贝.so文件到系统共享库路径下, 一般指/usr/lib(需要管理员权限)
    2、更改 LD_LIBRARY_PATH

  • 相关阅读:
    Java设计模式(三)结构性设计模式
    记录spring_boot 的web开发学习
    Flink源码阅读笔记——StreamGraph、JobGraph、ExecutionGraph
    【服务器数据恢复】华为某型号服务器raid6数据恢复案例
    spring-boot-maven-plugin
    C# 结构体转字节数组
    软件测试中如何测试算法?
    vs2022生成失败怎么解决
    处理:对于移动类型Z21和帐户8051010100 供应商货物移动 (014)的不同的字段选择
    git的介绍、git的功能特性、git工作流程、git 过滤文件、git多分支管理、远程仓库、把路飞项目传到远程仓库(非空的)、ssh链接远程仓库,协同开发
  • 原文地址:https://blog.csdn.net/dongming8886/article/details/132997945