详解Linux基础IO--干货满满

目录

  • 重谈文件操作–C语言
  • 介绍stdin,stdout,stderr
  • open接口
  • 访问文件的本质
  • 重定向
  • 理解Linux一切皆文件
  • 详解缓冲区
  • 实现fclose fwrite fopen
  • 认识硬件–磁盘
  • 理解文件系统
  • 理解软硬链接
  • 补充知识
  • 理解动静态库

重谈文件操作–C语言

fopen接口
第一个参数:const char* path——打开文件的名称
第二个参数:有“w”选项,“r”选项, “a”选项(其他选项暂时不讲)
返回类型:文件流(文件流是什么?后面会将)
在这里插入图片描述

以“w”作为选项的fopen函数测试

int main()
{
   FILE* fp = fopen("log.txt", "w");
   return 0;
}

在这里插入图片描述
我们能看到运行后会默认在当前路径下创建名为“log.txt”的一个文件。


当前路径下的本质是什么呢?

进程的工作目录

如何证明:

我们可以看到当前进程的工作目录
在这里插入图片描述

我们尝试修改进程的工作目录

int main()
{
    int n = chdir("/tmp/");//谁调用我,我就修改谁的工作目录
    if (n < 0)
    {
        cout << strerror(errno) << endl;
        exit(1);
    }
    FILE* fp = fopen("log.txt", "w");
    return 0;
}

在这里插入图片描述
可以看到我们修改了进程的工作目录,创建的文件到这个文件夹里面


测试“w”选项

int main()
{
    FILE* fp = fopen("log.txt", "w");
    char buffer[128];
    snprintf(buffer, sizeof(buffer), "aaaaaaaa");
    fwrite(buffer, strlen(buffer), 1, fp);
    return 0;
}

在这里插入图片描述

将aaaaaaa写入log.txt文件
再测试

int main()
{
    FILE* fp = fopen("log.txt", "w");
    // char buffer[128];
    // snprintf(buffer, sizeof(buffer), "aaaaaaaa");
    // fwrite(buffer, strlen(buffer), 1, fp);
    return 0;
}

在这里插入图片描述
可以看到只用"w"选项打开文件就会清空文件内容


那我们想不清空呢?

测试"a"选项

int main()
{
    FILE* fp = fopen("log.txt", "a");
    char buffer[128];
    snprintf(buffer, sizeof(buffer), "aaaaaaaa");
    fwrite(buffer, strlen(buffer), 1, fp);
    return 0;
}

在这里插入图片描述
可以看到 "a"是以追加的方式写文件

介绍stdin,stdout,stderr

stdin是标准输入流,stdout是标准输出流,stderr是标准错误流
这三个流是默认被系统打开的


测试stdout
向stdout文件流里面写入,而stdout是标准输出流,所以现象就是打印到屏幕上

int main()
{
    for (int i = 0; i < 5; ++i)
    {
        fprintf(stdout, "hello world%d\n", i + 1);
    } 
    return 0;
}

在这里插入图片描述

测试stdin
从键盘上读取数据

int main()
{
    FILE* fp = fopen("log.txt", "w");
    char buffer[128];
    int n = fread(buffer, 10, 1, stdin);
    buffer[strlen(buffer)] = '\0';

    printf("%s", buffer);
    return 0;
}

在这里插入图片描述

stderr和stdout的使用效果一样,(他们有什么区别呢?后面会说)


open接口

在这里插入图片描述
第一个参数pathname,文件名
第二个参数,flags是打开文件的方式---------选项有O_CREAT, O_WRONLY, O_TRUNC, O_RDONLY
第三个参数,创建文件时赋予文件的权限
第一个函数使用场景:文件已经存在
第二个函数使用的场景:文件不存在


O_CREAT, O_WRONLY, O_TRUNC, O_RDONLY本质是宏,是用的位来标记的
位图标记是什么呢?


#define ONE (1 << 0)//1
#define TWO (1 << 1)//2
#define THREE (1 << 2)//4
#define FOUR (1 << 3)//8

void show(int flags)
{
    if (flags & ONE) printf("hello function1\n");
    if (flags & TWO) printf("hello function2\n");
    if (flags & THREE) printf("hello function3\n");
    if (flags & FOUR) printf("hello function4\n");
}

int main()
{
    printf("------------------------------\n");
    show(ONE);
    printf("------------------------------\n");
    show(ONE | FOUR);
    printf("------------------------------\n");
    show(ONE | THREE);
    printf("------------------------------\n");
    show(ONE | TWO | THREE | FOUR);
    printf("------------------------------\n");
    return 0;
}

在这里插入图片描述


测试O_CREAT, O_WRONLY

int main()
{
    int fd = open("log.txt", O_CREAT | O_WRONLY, 0666);
    char buffer[128];
    snprintf(buffer, sizeof(buffer), "hello world");

    //这里不用将\0写进文件,因为字符串后面有\0是C语言的规定,和文件系统没关系
    write(fd, buffer, strlen(buffer));

    return 0;
}

在这里插入图片描述

再将写入内容改为aaaa

int fd = open("log.txt", O_CREAT | O_WRONLY, 0666);
    char buffer[128];
    snprintf(buffer, sizeof(buffer), "aaaa");

    //这里不用将\0写进文件,因为字符串后面有\0是C语言的规定,和文件系统没关系
    write(fd, buffer, strlen(buffer));

在这里插入图片描述
这里说明了O_WRONLY是覆盖式的写入

再查看文件类型

在这里插入图片描述

为什么这里的文件权限是664,我们给的权限不是666?

因为操作系统有文件的权限掩码————>不懂权限掩码的可以点击这里跳转
我们就想要666的文件权限怎么办呢?
在这里插入图片描述

int main()
{
    umask(0);
    int fd = open("log.txt", O_CREAT | O_WRONLY, 0666);
    char buffer[128];
    snprintf(buffer, sizeof(buffer), "aaaa");

    //这里不用将\0写进文件,因为字符串后面有\0是C语言的规定,和文件系统没关系
    write(fd, buffer, strlen(buffer));
    return 0;
}

在这里插入图片描述
这里的umask影响的是进程的,不会影响整个操作系统


测试O_CREAT,O_WRONLY,O_TRUNC
打开文件即清空文件,再写入
在这里插入图片描述

int main()
{
    umask(0);
    int fd = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);
    char buffer[128];
    snprintf(buffer, sizeof(buffer), "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");

    //这里不用将\0写进文件,因为字符串后面有\0是C语言的规定,和文件系统没关系
    write(fd, buffer, strlen(buffer));
    return 0;
}
int main()
{
    umask(0);
    int fd = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);
    char buffer[128];
    snprintf(buffer, sizeof(buffer), "bbbbbbbbbbb");

    //这里不用将\0写进文件,因为字符串后面有\0是C语言的规定,和文件系统没关系
    write(fd, buffer, strlen(buffer));
    return 0;
}

测试O_CREAT,O_WRONLY,O_APPEND

int main()
{
    umask(0);
    int fd = open("log.txt", O_CREAT | O_WRONLY | O_APPEND, 0666);
    char buffer[128];
    snprintf(buffer, sizeof(buffer), "aaaaaa\n");

    //这里不用将\0写进文件,因为字符串后面有\0是C语言的规定,和文件系统没关系
    write(fd, buffer, strlen(buffer));
    return 0;
}

在这里插入图片描述

访问文件的本质

文件其实是在磁盘上的,磁盘就是访问外设,访问磁盘文件就是在访问外设
根据计算机体系结构可知:几乎所有的库,只要是访问硬件设备,必定要封装系统调用!!
所以我们可推出C语言的库函数printf/fprintf/fscanf/fwrite/fread/fgets/fopen都对进行了系统调用接口的封装

在这里插入图片描述

C语言:
“w”------>O_WRONLY| O_CREAT | O_TRUNC, 0666
“r”------->O_WRONLY | O_CREAT | O_APPEND, 0666


在这里插入图片描述
则可说明open函数返回的本质是数组下标


证明:

int main()
{
    int fd1 = open("log1.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);
    int fd2 = open("log1.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);
    int fd3 = open("log1.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);
    int fd4 = open("log1.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);

    printf("fd1: %d\n", fd1);
    printf("fd2: %d\n", fd2);
    printf("fd3: %d\n", fd3);
    printf("fd4: %d\n", fd4);

    
    return 0;
}

在这里插入图片描述

为什么是从3开始呢?因为0、1、2默认被标准输入、标准输出、标准错误占用了

证明:

int main()
{
    close(1);//将标准输出关闭
    int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    printf("hello world\n");

    return 0;
}

按照预期,应该向屏幕上打印hello world
在这里插入图片描述
可以看到并没有向文件里写入,而是写入到了文件里。
因为下标为1的数组内容不在是标准输出流的信息,而换为了log.txt的信息


那么FILE 和 fd有什么关系呢?

int main()
{
    printf("stdin->fd: %d\n", stdin->_fileno);
    printf("stdin->fd: %d\n", stdout->_fileno);
    printf("stdin->fd: %d\n", stderr->_fileno);

    return 0;
}

在这里插入图片描述

在这里插入图片描述
可以看出FILE是封装了fd的结构体

重定向

文件描述符对应的分配规则是什么

从0下标开始,寻找最小的没有使用的数组位置,它的下标就是新文件的文件描述符

证明:

int main()
{
    close(1);
    int fd = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);
    const char* msg = "hello Linux\n";
    int cnt = 5;
    while (cnt)
    {
        write(1, msg, strlen(msg));
        cnt--;
    }
    close(fd);

    return 0;
}

在这里插入图片描述

本来要向屏幕打印,但没有打印到屏幕,而是写到了文件里面

在这里插入图片描述
这就是输出重定向:本来应该写到显示器上,却写到了文件里

重定向的本质:上层用的fd不变,在内核中更改fd对应的struct file*的地址


这样的话也很麻烦,所以系统也提供了这样的接口,让我们能更好的使用

在这里插入图片描述
接口作用:在struct file* fd_array[]里将以fildes为下标的内容覆盖以fildes2为下标的内容

int main()
{
    int fd = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);

    //重定向
    dup2(fd, 1);
    close(fd);
    const char* msg = "hello Linux\n";
    int cnt = 5;
    while (cnt)
    {
        write(1, msg, strlen(msg));
        cnt--;
    }
    close(fd);

    return 0;
}

在这里插入图片描述
可以看出和之前的代码是相同的效果

追加重定向实现
在这里插入图片描述
将上述代码一点就实现了追加重定向

输入重定向实现

int main()
{
    int fd = open("log.txt", O_RDONLY);

    dup2(fd, 0);
    
    char buffer[128];
    int s = read(0, buffer, sizeof(buffer) - 1);

    printf("%s", buffer);
    close(fd);
    return 0;
}

在这里插入图片描述
本来要从键盘里读取的,但从文件里读取了


stdout和stderr有什么区别?

int main()
{
    int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC);

    dup2(fd, 1);
    fprintf(stderr, "hello error message\n");
    fprintf(stderr, "hello error message\n");
    fprintf(stderr, "hello error message\n");
    fprintf(stderr, "hello error message\n");
    fprintf(stdout, "hello normal message\n");
    fprintf(stdout, "hello normal message\n");
    fprintf(stdout, "hello normal message\n");
    fprintf(stdout, "hello normal message\n");


    close(fd);
    return 0;
}

在这里插入图片描述
一个文件描述符为1另一个文件描述符为2

如何理解Linux一切皆文件

在这里插入图片描述

详解缓冲区

int main()
{
    const char* str = "hello write\n";
    const char* fstr = "hello fwrite\n";

    //C函数调用
    fprintf(stdout, "hello fprintf\n");
    fwrite(fstr, strlen(fstr), 1, stdout);
    printf("hello printf\n");
    //系统调用
    write(1, str, strlen(str));

    fork();

    return 0;
}

在这里插入图片描述
正常,符合我们的预期
再进行重定向
在这里插入图片描述

问题一:为什么C接口的函数重定向后,在该文件里写了两次?


int main()
{
    const char* fstr = "hello fwrite";

    //C函数调用
    fprintf(stdout, "hello fprintf");
    fwrite(fstr, strlen(fstr), 1, stdout);
    printf("hello printf");

    close(1);

    return 0;
}

在这里插入图片描述

问题二:为什么没有打印出来,也不能重定向到文件里?而加了\n能打印出来?

int main()
{
    const char* fstr = "hello fwrite\n";

    //C函数调用
    fprintf(stdout, "hello fprintf\n");
    fwrite(fstr, strlen(fstr), 1, stdout);
    printf("hello printf\n");

    close(1);

    return 0;
}

在这里插入图片描述


从问题一我们能看出C语言接口写了两次,而系统调用只写了一次。
说明这个缓冲区一定不在操作系统内部,不是系统级的缓冲区。

从问题二我们可以看出c接口没打印出来,缓冲区没刷新
说明c接口无\n不会刷新它的缓冲区

缓冲区剖析图在这里插入图片描述

缓冲区刷新策略
a. 有立即刷新 b.行刷新 c.满刷新 d.用户强制刷新 e.进程退出


为什么要有缓冲区?
1.提高读写效率 2.适配格式化

缓冲区就如我们生活中的快递站。
1.我们能直接将东西(数据)给快递站,不用自己去--------效率高
2.快递公司不能只为你一个快递去送快递,除非有特殊需求---------缓冲区的刷新策略
printf(“%d”,a);输入的时候需要格式化输入,而缓冲区能配合格式化转换为字符串存储到缓冲区

缓冲区在哪里呢?
FILE结构体里面,FILE里面还有对应打开的文件,缓冲区字段和维护信息

回归问题一
在这里插入图片描述
向屏幕打印采用的是行刷新,所以遇到\n就打印了
在这里插入图片描述
重定向到了log.txt文件,则不再采用行刷新了而是满刷新,遇到\n不会再刷新缓冲区
我们fork之后,退出进程,会导致缓冲区刷新,而刷新缓冲区本质就是修改数据,则缓冲区会被写时拷贝一份,使父进程和子进程都有各自的一份缓冲区了,两个进程退出,会刷新两次缓冲区,所以会有两份。

实现fclose fwrite fopen


#define FLUSH_NOW 1
#define FLUSH_LINE 2
#define FLUSH_ALL 4
const int SIZE = 128;

struct myFILE
{
    int fileno;
    char buffer[SIZE];
    int flag;
    int pos;
};


myFILE* myfopen(const char* filename, const char* model)
{
    int tfd = -1;
    if (strcmp(model, "w") == 0)
    {
        tfd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666);
    }
    else if (strcmp(model, "r") == 0)
    {
        tfd = open(filename, O_RDONLY);
    }
    else if (strcmp(model, "a") == 0)
    {
        tfd = open(filename,  O_CREAT | O_WRONLY | O_APPEND, 0666);
    }
    else 
    {
        //其他选项...
    }
    myFILE* file = new myFILE;
    file->fileno = tfd;
    file->pos = 0;
    file->flag = FLUSH_ALL;
    //file->flag = FLUSH_LINE;//需要手动更改
    return file;
}

int myfwrite(myFILE* file, const char* str, size_t size)//简单实现--接口不同
{
    memcpy(file->buffer + file->pos, str, size);
    file->pos += size;
    if (file->flag & FLUSH_ALL)
    {
        if (file->pos == SIZE)
        {
            write(file->fileno, str, size);
            file->pos = 0;
        }
    }
    else if (file->flag & FLUSH_LINE)
    {
        for (int i = 0; i < file->pos; ++i)
        {
            if (file->buffer[i] == '\n')
            {
                write(file->fileno, str, size);
                file->pos = 0;
            }
        }
    }
    else if (file->flag & FLUSH_NOW)
    {
        write(file->fileno, str, size);
        file->pos = 0;
    }
    return size;
}

void myfflush(myFILE* file)
{
    if (file->pos > 0)
    {
        write(file->fileno, file->buffer, file->pos);
        file->pos = 0;
    }
}
void myfclose(myFILE* file)
{
    if (file == nullptr) return;
    myfflush(file);
    close(file->fileno);
    delete file;
}

测试:

#include "mystdio.hpp"

int main()
{
    myFILE* fp = myfopen("log.txt", "w");
    const char* str = "hello myfwrite\n";
    myfwrite(fp, str, strlen(str));

    myfclose(fp);
    return 0;
}

在这里插入图片描述


认识硬件–磁盘

上面谈的是被打开的文件,那么未被打开的文件是如何被管理的呢?
1.路径问题2.存储问题3.获取的问题(文件属性+文件内容)4.效率

磁盘的物理结构:
在这里插入图片描述
如何定位一个扇区呢?
先定位在哪一个磁头---------就能定位在哪个面了
再定位磁道---------磁头来回摆动的时候就是在定位磁道
再定位对应的扇区-----------盘片旋转的时候就是让磁头定位扇区
这种硬件的定位方式叫做:CHS定位法

磁盘的逻辑结构:
将圆形结构抽象成直线----数组的形式
在这里插入图片描述
只要知道这个扇区的下标,就可以定位一个扇区了

在操作系统内,我们称这种地址为LBA(Logic Block Address 逻辑块地址)
这里就形成了逻辑扇区地址和物理地址的互相转换-----------LBA<----->CHS


不仅仅CPU有寄存器,其他外设也有(如磁盘)------硬件可以被CPU访问
在这里插入图片描述

理解文件系统

文件=文件属性+文件内容

Linux的文件属性是分批存储的,文件属性存储到inode里,文件内容存到data block里。一个文件只有一个inode,一个数据块只会被一个文件使用,可以通过inode找到对应的数据块。
在这里插入图片描述

问题:一个inode里面存了15个数据块的编号,那不是一个数据块只有4kb,那不是一个文件里只能存4*15kb?
这些数据块编号也分为直接索引和间接索引,直接索引的数据块保存的直接是文件的内容,而间接索引的数据块里面存的不是文件的内容,而存的是其他数据块编号。4kb * 1024 / 4(int的大小) = 1024个块,又可以索引1024个数据块了。

一个磁盘假设有500GB,500GB太大了不好直接管理,我们则采用分治的策略,分为若干个区域管理(100GB、100GB、150GB、150GB),但每个区任有100GB、任然不好直接管理,再进行分组,分为大约为5GB的组。每个组将这5GB的管理好即管理好了500GB
在这里插入图片描述

格式化:每一个分区再被使用之前,都必须提前将部分文件系统的属性(Super Block、Group Descriptor Table、Block Bitmap、inode Bitmap)提前设置就对应的分区中,方便我们后续使用这个分区或者分组。

OS中,对一个文件进行操作的时候,统一使用的是:inode编号
创建一个文件会发生什么事?先通过文件的路径找到分区,再从一个组里面通过inode Bitmap找到一个空闲的inode,并由0置1,填充inode信息,遍历BlockBitmap为它分配数据块个数,再将数据写入数据块中。
删除一个文件会发生什么事?先通过文件的路径找到分区,再通过inode找到分组,再找到其inode Bitmap并由1置0即可,blockMap也同样由1置0即可
对一个文件进行操作的时候,统一使用的是inode编号。问题:我们使用的时候不是inode,是文件名啊

如何理解目录:目录也是文件,也有自己的inode,当然也会有自己的数据块,那目录里的数据块存的是什么呢?存的是当前目录下文件名和inode的映射关系。

我们如何对一个文件进行操作呢?目的是找到其inode。文件名有了,如何找到文件名映射的inode的呢?目录里存的是文件名和inode的映射关系,我们则要找到该文件的目录,才能找到其文件名,和inode的映射关系,才能找到该文件的inode,才能对该文件进行操作。而目录的inode又如何找到呢?则要找到目录的目录,从此循环递归。所以是从根目录一直往下找,找到该文件。
这也就是为什么同一个目录下不能有同名文件----文件名和inode的映射必须是唯一的

那这样每次都要从根开始递归下去找文件不是很慢?系统里有dentry缓存,提高了效率,这里不做过多讲解


理解软硬链接

软链接命令:ln -s [要链接的文件名] [软链接名]

在这里插入图片描述

软链接就如window下的快捷方式
在这里插入图片描述
可以看到要运行code文件,目录很深,不好操作
对目标文件建立软链接
在这里插入图片描述

硬链接命令:ln [要链接的文件名] [硬链接名]

在这里插入图片描述

取消链接命令: unlink [链接的文件名]
在这里插入图片描述

软硬链接的区别是什么?
是否具有独立的inode。软链接有独立的inode,可以被当作一个独立的文件来看待,而硬链接是没有独立的inode
在这里插入图片描述
建立硬链接,没有分配到独立的inode,则你一定用的是别人的inode。
建立硬链接本质:在目录下创建一个文件名映射同一个inode
也可以看到其硬链接数变为了2
在这里插入图片描述
删除myfile.txt文件,发现它的硬链接任然存在,并且和myfile.txt一样可以使用
在这里插入图片描述
什么时候一个文件算作被真正的删除呢?当一个文件的硬链接数变成0的时候

在这里插入图片描述
为什么创建了一个目录它的硬链接数直接为2了呢?因为目录里面还有.目录
在这里插入图片描述

Linux系统为什么不允许对目录建立硬链接
在这里插入图片描述
因为会引起环路问题:在这里插入图片描述

补充知识

物理内存和磁盘文件交换数据的单位为4KB
页框:物理内存的单位
页帧:磁盘文件中划分为的单位

在这里插入图片描述

为什么是4KB为单位呢?
1.在硬件上,若为1KB则会增多IO访问的次数,导致效率变慢
2.在软件上,能基于局部性原理–预加载机制

操作系统是如何管理内存的?
先描述,再组织
将物理内存中的每个单位用结构体描述起来,再用数组把它们给组织起来,对物理内存管理就变为了对数组内容的增删查改
struct page{
unsigned long flags
atomic_t _count;
…//属性
};
struct page mem_array[1047576];
4GB的空间会有1048576个页框,访问一个内存如0x11223344,只需&0xFFFFF000即可找到该内存对应的页号,找到其页号也就找到了其页框,则可对这一块内存进行操作了

每个文件打开都有属于自己的inode属性和文件页缓冲区。文件页缓冲区也就是之前提到的内核级缓冲区
在这里插入图片描述

动静态库

静态库命名方案:libXXX.a
动态库命名方案:libYYY.so

//mymath.h文件
extern int myerrno;
int Div(int x, int y);
int Mul(int x, int y);
int Add(int x, int y);
int Sub(int x, int y);
//mymath.cc文件
#include "mymath.h"
int myerrno = 0;
int Div(int x, int y)
{
    if (y == 0)
    {
        myerrno = 1;
        return -1;
    }
    return x / y;
}
int Mul(int x, int y)
{
    return x * y;
}
int Add(int x, int y)
{
    return x + y;
}
int Sub(int x, int y)
{
    return x - y;
}
//test.cc文件
#include <iostream>
#include "mymath.h"
using namespace std;

int main()
{
    int a = 10;
    int b = 0;
    int ret = Div(a, b);
    cout << "a / b = " << ret << "  myerrno:" << myerrno << endl;
    return 0;
}

当你是创作者,你要将你提供的方法给别人用,有两种方案:一是直接给源代码,二是不想给源代码,把自己的方法生成.o二进制文件给对方。最后再给.h文件(.h文件就相当于说明书,说明如何使用你的方法)
库的本质:将众多.o文件的集合

如何打包成静态库?
ar -rc libXXX.a [要打包的.o文件]
在这里插入图片描述


在这里插入图片描述

现在我们要使用这个库,如何使用呢?
在这里插入图片描述
编译出错,原因没包头文件
g++的-I选项:能帮你在这个路径下面找头文件
在这里插入图片描述
但还是出错了
g++的-L选项:能帮你找到库的位置
在这里插入图片描述
还是出错了
g++的-l选项:告诉要链接的库名的名字(注意:这里的名字不是libmymath.a,库名是去掉前缀和后缀)
在这里插入图片描述
终于成功了

ldd可以查看链接状态
在这里插入图片描述
为什么没有看到链接的mymath库呢?
g++默认的是动态链接,链接一个库,若系统中既链接了动态库又链接了静态库,则g++显示链接的是动态库,若只有静态库,则会显示静态库;若只有动态库,则会显示动态库

每次使用库都这么麻烦,有没有简单的方法?

1.将头文件和库放到系统会默认去查找的路径下面
在这里插入图片描述
这种操作的本质叫安装
在这里插入图片描述
为什么还是找不到库在哪里?
第三方库一定要使用g++的-l选项
在这里插入图片描述

2.在系统默认的文件路径下建立软链接
在这里插入图片描述
因为你在/usr/include/文件下链接的是myinclude目录,所以在头文件包含时要写成#include “myinclude/mymath.h”
如果你直接链接的是mymath.h头文件则不需要这样写,按照原来的#include “mymath.h”即可
在这里插入图片描述


如何形成动态库?
第一步:g++的-fPIC选项生成位置无关码。什么是位置无关码?后面会讲
在这里插入图片描述
第二步:g++的--shared选项生成动态库。
在这里插入图片描述

在这里插入图片描述
生成可执行文件
在这里插入图片描述
运行可执行文件
在这里插入图片描述
为什么运行失败呢?不是告诉了g++库文件路径和库名称码?而静态库可以直接运行呢?
当你把程序编译,和gcc已经没有关系了,运行的时候OS和shell也要知道库在哪里的,而你的库没有在系统路径下,OS无法找到。而静态库是在编译的时候就将.o文件编译到可执行文件里了,已经是可执行文件的一部分了。
任然可用之前的方案
在这里插入图片描述
在这里插入图片描述

动态库静态库原理
动态库在进程运行的时候,是要被加载的(静态库没有),并且所有可执行程序都可能用到同一个动态库,所以动态库被系统加载之后是会被所以进程共享的。

在可执行程序被编译好时,其内部代码是否有地址?
有,不仅OS里有进程地址空间,并且可执行程序也是按照进程地址空间(虚拟地址)进行编译的

若将动态库的地址加载固定的共享区地址是不可能的,因为你这个库不知道其他的库是否也映射到了这个位置,所以自己函数内部的代码只能采用偏移量。
而产生这个偏移量的过程就是g++的-fPIC选项,生成与位置无关码
在这里插入图片描述

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值