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

news2024/11/24 9:04:40

1. 认识IO

什么是IO

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

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

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

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

2.C文件接口的IO

写文件

#include <stdio.h>
#include <string.h>
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;
}

image-20230909160750952

读文件

#include <stdio.h>
#include <string.h>
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;
}

image-20230909161531953

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

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

接口介绍

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
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

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

写文件

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
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;
}

读文件

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
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;
}

open函数返回值

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

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

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

文件描述符fd

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

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

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
    int fd=open("myfile",O_RDONLY);
    if(fd<0){
        perror("open");
    }
    printf("%d\n",fd);
    close(fd);
    return 0;
}
//输出结果
//3

文件描述符就是一个小整数,但它为什么是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 <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
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!!!

文件描述符的分配规则

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

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

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
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;
}

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

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

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
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;
}

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

重定向

重定向的本质是什么呢?

image-20230910125105172

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

dup2 系统调用

函数原型如下:

#include <unistd.h>
int dup2(int oldfd, int newfd);
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
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;
}

image-20230910130802335

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

FILE

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

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
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

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

[hdm@centos7 BasicIO]$ ./test  >myfile 
[hdm@centos7 BasicIO]$ cat myfile 
hello write
hello printf
hello fprintf
hello printf
hello fprintf

我们发现 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

每行包含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: -

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

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

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

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
[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

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
[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

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 <stdio.h>
#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));
}
[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 指定库名
测试目标文件生成后,静态库删掉,程序照样可以运行。

库搜索路径

  • 从左到右搜索-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

使用动态库

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1020782.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

生命周期简化idea配置

生命周期简图 扩展接口介绍 2.1 Aware接口 在spring中Aware接口表示的是感知接口&#xff0c;表示spring框架在Bean实例化过程中以回调的方式将特定在资源注入到Bean中去&#xff08;如&#xff1a;ApplicationContext, BeanName,BeanFactory等等&#xff09;。Aware接口本事没…

Java——break、continue(学习笔记)

1.break(主要与switch搭配使用) 在任何循环语句的主体部分&#xff0c;均可用break控制循环的流程。break用于强行退出循环&#xff0c;不执行循环中剩余的语句。 2.continue 用在循环语句体中&#xff0c;用于终止某次循环过程&#xff0c;即跳过循环体中尚未执行的语句&am…

u盘内容防止复制(U盘内数据防拷贝的方法)

随着科技的发展&#xff0c;U盘已经成为我们日常生活和工作中不可或缺的一部分。然而&#xff0c;U盘的普及也带来了一些问题&#xff0c;如数据泄露、病毒传播等。因此&#xff0c;保护U盘中的数据安全变得尤为重要。 方法一&#xff1a;设置文件权限 打开U盘&#xff0c;找到…

轻量级c语言开源日志库log.c介绍 - 实现不同级别和参数化日志打印

前言 c语言没有现成的日志库&#xff0c;如果要记录日志&#xff0c;需要自己封装一个日志库。如果要实现日志级别和参数打印&#xff0c;还是比较麻烦的&#xff0c;正好在github找到了一个c语言开源日志库&#xff0c;可以实现日志级别打印&#xff0c;参数打印&#xff0c;…

LVGL移植win端模拟显示流畅解决方案-使用 SquareLine 生成前端 UI 文件

lvgl_port_win_vscode 在 win 平台对 lvgl 方便的进行模拟显示&#xff0c;程序文件结构清晰&#xff0c;lvgl with SDL2&#xff0c;cmake 构建&#xff0c;VsCode 一键运行&#xff0c;使用 SquareLine 生成前端 UI 文件&#xff0c;win 上直接跑。 相比官方的 lvgl 移植到…

一百七十九、Linux——Linux报错No package epel-release available

一、目的 在Linux中配置Xmanager服务时&#xff0c;执行脚本时Linux报错No package epel-release available 二、解决措施 &#xff08;一&#xff09;第一步&#xff0c;# wget http://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm &#xff08;二&…

@Validated 和 @Valid 的区别,你真的懂吗?SpringBoot 参数校验必知必会!

概述 Valid是使用Hibernate validation的时候使用Validated是只用Spring Validator校验机制使用 说明&#xff1a;java的JSR303声明了Valid这类接口&#xff0c;而Hibernate-validator对其进行了实现 Validation对Valid进行了二次封装&#xff0c;在使用上并没有区别&#xff…

水一下文章

前言&#xff1a;相信看到这篇文章的小伙伴都或多或少有一些编程基础&#xff0c;懂得一些linux的基本命令了吧&#xff0c;本篇文章将带领大家服务器如何部署一个使用django框架开发的一个网站进行云服务器端的部署。 文章使用到的的工具 Python&#xff1a;一种编程语言&…

zookeeper —— 分布式服务协调框架

zookeeper —— 分布式服务协调框架 一、Zookeeper概述1、Zookeeper的基本概念2、Zookeeper的特点3、Zookeeper的数据结构 二、Zookeeper的安装部署1、Zookeeper的下载2、Zookeeper的安装本地模式&#xff08;单机模式standalone&#xff09;安装部署分布式&#xff08;集群模式…

视频监控系统/视频汇聚平台EasyCVR对国标类型编码进行判断的实现方式

视频监控平台/视频存储/视频分析平台EasyCVR基于云边端一体化管理&#xff0c;支持多类型设备、多协议方式接入&#xff0c;具体包括&#xff1a;国标GB28181协议、RTMP、RTSP/Onvif、海康Ehome&#xff0c;以及海康SDK、大华SDK、华为SDK、宇视SDK、乐橙SDK、萤石SDK等&#x…

WebGL 计算点光源下的漫反射光颜色

目录 点光源光 逐顶点光照&#xff08;插值&#xff09; 示例程序&#xff08;PointLightedCube.js&#xff09; 代码详解 示例效果 逐顶点处理点光源光照效果时出现的不自然现象 更逼真&#xff1a;逐片元光照 示例程序&#xff08;PointLightedCube_perFragment.js…

paddlespeech asr脚本demo

概述 paddlespeech是百度飞桨平台的开源工具包&#xff0c;主要用于语音和音频的分析处理&#xff0c;其中包含多个可选模型&#xff0c;提供语音识别、语音合成、说话人验证、关键词识别、音频分类和语音翻译等功能。 本文介绍利用ps中的asr功能实现批量处理音频文件的demo。…

回溯算法 解题思路

文章目录 算法介绍回溯算法能解决的问题解题模板1. 组合问题2. N皇后问题 算法介绍 回溯法&#xff08;Back Tracking Method&#xff09;&#xff08;探索与回溯法&#xff09;是一种选优搜索法&#xff0c;又称为试探法&#xff0c;按选优条件向前搜索&#xff0c;以达到目标…

URL 管理器

基本介绍 对外接口 对外提供两个接口&#xff1a;一个可以提取URL&#xff0c;一个可以增加URL&#xff0c;分别对应图上的1和2。 当要爬取某个网页时&#xff0c;则可以从1接口提取出该网页的URL进行爬取。 有时候爬取的网页内容中会包含别的网页链接&#xff0c;即包含有U…

java版Spring Cloud+Mybatis+Oauth2+分布式+微服务+实现工程管理系统

鸿鹄工程项目管理系统 Spring CloudSpring BootMybatisVueElementUI前后端分离构建工程项目管理系统 1. 项目背景 一、随着公司的快速发展&#xff0c;企业人员和经营规模不断壮大。为了提高工程管理效率、减轻劳动强度、提高信息处理速度和准确性&#xff0c;公司对内部工程管…

Sui zkLogin让真正链接10亿用户成为可能

近日&#xff0c;Sui宣布推出zkLogin&#xff0c;这是将用户引入链上的最简单方式。zkLogin是Sui的一种原生功能&#xff0c;允许用户使用来自Google和Twitch等现有的Web2身份验证登录Web3应用程序&#xff0c;消除了用户需要记住或记录私钥的流程。 创建钱包通常被认为是区块…

使用vite创建vue3项目及项目的配置 | 环境准备 ESLint配置 prettier配置 husky配置 项目继承

文章目录 使用vite创建vue3项目及项目的配置1.环境准备2.项目配置ESLint校验代码工具配置 - js代码检测工具1.安装ESLint到开发环境 devDependencies2.生成配置文件:.eslint.cjs**3.安装vue3环境代码校验插件**4. 修改.eslintrc.cjs配置文件5.生成ESLint忽略文件6.在package.js…

K8S pod资源、探针

目录 一.pod资源限制 1.pod资源限制方式 2.pod资源限制指定时指定的参数 &#xff08;1&#xff09;request 资源 &#xff08;2&#xff09; limit 资源 &#xff08;3&#xff09;两种资源匹配方式 3.资源限制的示例 &#xff08;1&#xff09;官网示例 2&#xff0…

张勇时代落幕 蔡崇信能否让阿里变得更好

这两年&#xff0c;互联网行业似乎迎来了组织变革潮&#xff0c;只是谁也没想到&#xff0c;阿里的来得这么快&#xff0c;这么彻底。 9月10日晚&#xff0c;阿里巴巴董事会主席蔡崇信发布全员信&#xff0c;宣布已按计划完成集团管理职务交接&#xff0c;由他接任集团董事会主…

【JavaScript】对象类似数组那种数据结构 搜索一组匹配的数据

在 JavaScript 中&#xff0c;如果您想在类似数组的对象中进行关键字搜索并找到一组匹配的数据&#xff0c;可以使用filter()方法结合正则表达式来实现。 以下是一个示例代码&#xff0c;演示如何在类似数组的对象中进行关键字搜索并找到匹配的数据&#xff1a; const obj {…