【Linux】文件描述符fd

1.前置预备

  1. 文件 = 内容 + 属性
  2. 访问文件之前,都必须先打开他
cpp 复制代码
#include<stdio.h>    
    
int main()    
{    
  FILE* fp=fopen("log.txt","w");    
  if(fp==NULL)    
  {    
    perror("fopen");    
    return 1;    
  }    
  fclose(fp);    
                                                                                                                                                             
  return 0;    
}    

把源代码编成可执行程序,并没有打开文件,当程序运行执行到fopen时,才被打开,fopen和malloc一样,都是运行时操作,

当访问一个文件时,是进程在进行访问,可是进程是在内存中被CPU调度,文件是在磁盘中的,

根据冯诺依曼,CPU不能直接访问到磁盘,所以,文件也必须被加载到内存中。
所以打开文件fopen,是在做什么?

把文件加载到内存中
进程 = 属性 + 内容,同样 ,文件 = 内容 + 属性,OS需要对进程进行管理,需不需要对加载到内存的文件进行管理呢?

必须要!!!
如果管理文件呢?

先描述,再组织,

在内核中,文件 = 文件的内核数据结构 + 文件的内容,

磁盘中,文件 = 文件属性 + 内容
结论:我们研究打开的文件,本质是在研究进程和文件的关系。

没有被打开的文件呢?在磁盘上

文件:

1.被打开的文件------加载到内存,

2.没有被打开的文件------磁盘

2.以"w"方式打开文件

先看用C语言打开一个文件,用"w"的方式打开一个文件log.txt,如果这个文件不存在,就新建,如果这个文件存在,则清空内容后打开,

在命令行上,可以通过**> 文件** 。对一个文件进行操作,> 意思跟"w"一样,文件不存在则创建,存在则对内容进行清空, > 也叫输出重定向

下面一段代码内容是文件不存在,新建然后进行数据插入:

cpp 复制代码
#include <stdio.h>

int main()
{
    //'w'文件不存在新建
    FILE *fp = fopen("./log.txt","w");
    if(fp == NULL)
    {
        perror("fopen");
        return 1;
    }
    const char *message = "hello file\n";
    int i = 0;
    while(i<10)
    {
        fputs(message,fp);
        i++;
    }
    fclose(fp);
    return 0;
}

下面一段代码是打开文件,然后关闭文件,不进行任何操作,会对文件内容进行清空:

cpp 复制代码
#include <stdio.h>

int main()
{
    //'w'文件不存在新建
    FILE *fp = fopen("./log.txt","w");
    if(fp == NULL)
    {
        perror("fopen");
        return 1;
    }
    // const char *message = "hello bit\n";
    // int i = 0;
    // while(i<10)
    // {
    //     fputs(message,fp);
    //     i++;
    // }
    fclose(fp);
    return 0;
}

3.以"a"方式打开文件

在命令行上,通过 >> 文件。对文件进行操作,>> 和"a"意思一样,对内容进行追加

先用"w"方式打开文件,每次执行该程序,都会先进行清空,无法进行追加:

cpp 复制代码
#include <stdio.h>

int main()
{
    //'w'文件不存在新建
    FILE *fp = fopen("./log.txt","w");
    if(fp == NULL)
    {
        perror("fopen");
        return 1;
    }

    char buffer[1024];
    const char *message = "hello bit";
    int i = 0;
    while(i<10)
    {
        snprintf(buffer,sizeof(buffer),"%s:%d\n",message,i);
        fputs(buffer,fp);
        i++;
    }
    fclose(fp);
    return 0;
}

这时可以用"a"方法进行打开文件,每次执行该程序,都会追加式的像文件进行插入:

cpp 复制代码
#include <stdio.h>

int main()
{
    //'a'追加内容
    FILE *fp = fopen("./log.txt","a");//append
    if(fp == NULL)
    {
        perror("fopen");
        return 1;
    }

    char buffer[1024];
    const char *message = "hello bit";
    int i = 0;
    while(i<10)
    {
        snprintf(buffer,sizeof(buffer),"%s:%d\n",message,i);
        fputs(buffer,fp);
        i++;
    }
    fclose(fp);
    return 0;
}

4.程序默认打开三个输入输出流

一个程序默认启动,会打开三个输入输出流,标准输入,标准输出,标准错误,在C语言中,底层硬件所对应的文件键盘与显示器,把他们包装成文件的样子,最后访问键盘显示器,就可以以文件FILE*指针的形式进行访问

总结:是进程会默认打开三个输入输出流

看看下面代码,把内容打印到显示器方法:可以通过stdout进行操作

cpp 复制代码
#include <stdio.h>

int main()
{
    printf("hello word\n");
    fputs("bit\n",stdout);
    fwrite("aaaaa\n",1,4,stdout);
    fprintf(stdout,"bbbb\n");
    return 0;
}

2.文件管理

往显示器上显示,往磁盘文件打开或写入文件,读写键盘,本质上是访问硬件,我们用户在访问硬件,不可能直接通过语言进行直接访问硬件的,必须要通过操作系统,

我们使用的C接口,看起来是直接访问硬件,其实是通过操作系统提供的系统调用接口,才能访问到硬件,所以我们使用的C接口,底层一定要封装对应的文件类的系统调用!!

fopen fclose fread fwrite 都是C标准库当中的函数,我们称之为库函数 (libc)。

⽽ open close read write lseek 都属于系统提供的接⼝,称之为系统调⽤接⼝

1.open

现在我们来认识一下文件系统调用接口:我们操作文件,首先是要打开文件,系统调用open

第一个参数是文件名,

第二个参数,实际上是一个32bit位,也就是一个位图

第三的参数,如果文件已经创建,就不需要带,如果文件没有被创建,就需要对文件进行权限赋值

我们通过下面一段代码来认识,位图参数的传递:

cpp 复制代码
#include <stdio.h>

#define ONE (1<<0)//1   000001
#define TWO (1<<1)//2   000010
#define THREE (1<<2)//4 000100
#define FOUR (1<<3)//16 001000
#define FIVE (1<<4)//32 010000

//code 1
void PrintTest(int flags)
{
    //都为1才为1,只要有一个不为1,就为0
    if(flags & ONE)
    {
        printf("one\n");
    }
    if(flags & TWO)
    {
        printf("two\n");
    }
    if(flags & THREE)
    {
        printf("three\n");
    }
    if(flags & FOUR)
    {
        printf("four\n");
    }
    if(flags & FIVE)
    {
        printf("five\n");
    }
}

int main()
{
    printf("=====================\n");
    PrintTest(ONE);
    printf("=====================\n");
    PrintTest(TWO);
    printf("=====================\n");
    PrintTest(THREE);
    printf("=====================\n");
    //只要两个操作数对应的位中有一个为1,那么结果位就为1。
    PrintTest(ONE | THREE);
    printf("=====================\n");
    PrintTest(ONE | TWO | THREE);
    printf("=====================\n");
    PrintTest(ONE | TWO | THREE | FOUR);
    printf("=====================\n");
    return 0;
}

所以我们可以通过 | 的方式进行传递参数,通俗点意思就是说,| 两边条件都满足。

open函数第二个参数:

  • O_RDONLY:以只读方式打开文件。
  • O_WRONLY:以只写方式打开文件。
  • O_RDWR:以读写方式打开文件。
  • O_CREAT : 若⽂件不存在,则创建它。需要使⽤mode选项,来指明新⽂件的访问 权限
  • O_TRUNC:如果文件已存在且成功打开,则将其长度截断为 0。
  • O_APPEND:写入时将数据追加到文件末尾。
    返回值: 成功:新打开的⽂件描述符 失败:-1

现在我们来使用一下open函数,第二个参数传递 O_WRONLY | O_CREAT,以只写方式打开,如果文件不存在则创建:

cpp 复制代码
#include <stdio.h>
#include <fcntl.h>


int main()
{
    //以只写方式打开,如果文件不存在则创建
    open("log.txt",O_WRONLY | O_CREAT);
    return 0;
}

这里我们发现新创建的文件,他的权限是错乱的,那是因为我们调用系统接口时,新创建文件,需要给文件进行权限设置,而我们语言级接口fopen不需要,是因为底层对其进行了封装。

所以用系统调用接口时,新建文件,还要告诉新建文件默认的起始权限是多少!!!!也就是第三个参数传递,传递权限。

下面代码我们给文件进行权限赋值666,意思就是文件权限为rw-rw-rw-,r = 4 w = 2 x = 1.

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>


int main()
{
    //以只写方式打开,如果文件不存在则创建
    open("log.txt",O_WRONLY | O_CREAT,0666);
    return 0;
}

此时我们就可以发现权限没有错乱,但同时又有一个问题第三方的全是为什么只要一个r?

因为系统里有个umask,他会默认屏蔽掉一些权限,系统的umask = 0002,结合666,就会编成664,所以文件的最终权限会结合umask值来进行最终确认。

我们在编写代码的时候,也可以进行设置umask值,如下代码所示:

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>


int main()
{
    //不让系统屏蔽某些权限
    umask(0);
    //以只写方式打开,如果文件不存在则创建
    open("log.txt",O_WRONLY | O_CREAT,0666);
    return 0;
}

对umask清0,最终文件权限就是是第三个参数传递的权限,对在代码中umask清0,并不会影响到系统的umask值。

1.每一个进程有一个umask值,表示创建文件umask权限,进程umask权限默认从系统中获得,但自己手umask权限,采用就近原则来直接使用用户的umask值

2.touch创建文件其实权限都是666,然后受umask影响编程664.

下面代码模拟实现touch:

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

//模仿touch
int main(int argc,char *argv[])
{
    //不让系统屏蔽某些权限
    //umask(0);
    //以只写方式打开,如果文件不存在则创建
    open(argv[1],O_WRONLY | O_CREAT,0666);
    return 0;
}

2.write

第一个参数传文件表示符,第二个参数传一个指向要写入的数据的缓冲区的指针,第三个参数表示写入的大小

3.read

第一个参数是文件标识符,第二个参数是一个指针,指向用于存储读取数据的缓冲区,第三个参数是该区域大小。

4.close

5.文件描述符fd

open返回值,成功时返回一个文件描述符,失败返回-1

现在我们看一下文件的返回值是多少,有什么用?

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main()
{
    //不让系统屏蔽某些权限
    //umask(0);
    //以只写方式打开,如果文件不存在则创建
    int fd1 = open("log.txt",O_WRONLY | O_CREAT,0666);
    if(fd1<0)
    {
        perror("open");
        return 0;
    }
    printf("fd1: %d\n",fd1);
    return 0;
}

此时文件标识符为3,为什么文件打开从3开始呢?

因为进程启动,默认打开了三个标准的输入输出流stdin,stdout,stderr,因为Linux下一切皆文件,这三个标准的输入输出流被当成文件打开了。

现在我们使用系统调用接口来进行文件写入,如下代码:

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main()
{
    //不让系统屏蔽某些权限
    //umask(0);
    //以只写方式打开,如果文件不存在则创建
    int fd1 = open("log.txt",O_WRONLY | O_CREAT,0666);
    if(fd1<0)
    {
        perror("open");
        return 0;
    }
    printf("fd1: %d\n",fd1);

    const char *message = "hello word\n";
    write(fd1,message,strlen(message));
    close(fd1);
    return 0;
}

紧接着,我们只修改一个message指向的内容,原本文件内容不变:

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main()
{
    //不让系统屏蔽某些权限
    //umask(0);
    //以只写方式打开,如果文件不存在则创建
    int fd1 = open("log.txt",O_WRONLY | O_CREAT,0666);
    if(fd1<0)
    {
        perror("open");
        return 0;
    }
    printf("fd1: %d\n",fd1);

    const char *message = "aaaaaa";

    write(fd1,message,strlen(message));
    close(fd1);
    return 0;
}

因为在做操作时只告诉了写入,并没有告诉要清空,只覆盖在原来基础上进行覆盖式的写入!!!

所以我们再加个选项O_TRUNC,如果文件存在则进行先清空:

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main()
{
    //不让系统屏蔽某些权限
    //umask(0);
    //以只写方式打开,如果文件不存在则创建,如果存在则清空
    int fd1 = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);
    if(fd1<0)
    {
        perror("open");
        return 0;
    }
    printf("fd1: %d\n",fd1);

    const char *message = "aaaaaa\n";

    write(fd1,message,strlen(message));
    close(fd1);
    return 0;
}

如果对该文件只进行了打开然后关闭不做写入:

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main()
{
    //不让系统屏蔽某些权限
    //umask(0);
    //以只写方式打开,如果文件不存在则创建,如果存在则清空
    int fd1 = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);
    if(fd1<0)
    {
        perror("open");
        return 0;
    }
    printf("fd1: %d\n",fd1);

    const char *message = "aaaaaa\n";

    //write(fd1,message,strlen(message));
    close(fd1);
    return 0;
}

内容被清空了,这样O_WRONLY | O_CREAT | O_TRUNC传参,最终作用就是和用fopen打开使用"w"方法一样,fopen使用"w"方法底层就是封装了这!!!

现在我们使用追加的形式进行写入:

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main()
{
    //不让系统屏蔽某些权限
    //umask(0);
    //以只写方式打开,如果文件不存在则创建,如果存在则清空
    //int fd1 = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);
    int fd1 = open("log.txt",O_WRONLY | O_CREAT | O_APPEND,0666);
    if(fd1<0)
    {
        perror("open");
        return 0;
    }
    printf("fd1: %d\n",fd1);

    const char *message = "aaaaaa\n";

    write(fd1,message,strlen(message));
    close(fd1);
    return 0;
}

在上一个log.txt基础上进行内容的追加。

这样O_WRONLY | O_CREAT | O_APPEND传参,最终作用就是和用fopen打开使用"a"方法一样,fopen使用"a"方法底层就是封装了这!!!

fopen fclose fread fwrite 都是C标准库当中的函数,我们称之为库函数 (libc)。

open close read write lseek 都属于系统提供的接⼝,称之为系统调⽤接⼝

fopen fclose fread fwrite底层就是对open close read write进行了封装

关于fd的问题:

我们连续打开几个文件,看看文件描述符是多少?

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main()
{
    //不让系统屏蔽某些权限
    //umask(0);
    //以只写方式打开,如果文件不存在则创建,如果存在则清空
    //int fd1 = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);
    int fd1 = open("log.txt",O_WRONLY | O_CREAT | O_APPEND,0666);
    int fd2 = open("log.txt",O_WRONLY | O_CREAT | O_APPEND,0666);
    int fd3 = open("log.txt",O_WRONLY | O_CREAT | O_APPEND,0666);
    int fd4 = open("log.txt",O_WRONLY | O_CREAT | O_APPEND,0666);
    if(fd1<0)
    {
        perror("open");
        return 0;
    }
    printf("fd1: %d\n",fd1);
    printf("fd2: %d\n",fd2);
    printf("fd3: %d\n",fd3);
    printf("fd4: %d\n",fd4);

    const char *message = "aaaaaa\n";

    write(fd1,message,strlen(message));
    close(fd1);
    return 0;
}

观察到文件描述符从3开始依次创建!!!

而前面我们说到,0,1,2被键盘,显示器,显示器占用,又说过,这些硬件在底层被包装成文件的形式,同样,我们能不能通过0,1,2进行对键盘文件,显示器文件进行写与读呢?

看下面代码:

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main()
{
    const char *message = "hello write\n";
    write(1,message,strlen(message));

    return 0;
}

我们通过write,直接向文件描述符1,进行写入message指向的内容,结果的确打印在屏幕上

我们再来看看下面调用read进行读:

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main()
{
    //abcd
    char buffer[128];
    ssize_t s = read(0,buffer,sizeof(buffer));
    if(s > 0)
    {
        //把回车改为0
        buffer[s-1] = 0;
        printf("%s\n",buffer);
    }

    return 0;
}

我们键盘输入abcd,然后打印,发现的确从标识符0进行读取。

内核角度:

我们来从内核角度进行观察,当我们磁盘中有个文件log.txt包含内容+属性,当我们把程序进行加载,就变成了进程,OS为了对其进行管理,就有了PCB,CPU调度该进程,执行到open函数,此时打开文件,也就是把磁盘中文件加载到内存,加载到内存中,那么OS也要对其进行管理,先描述再组织,OS要为我们文件创建一个数据结构struct file,里面包含文件的属性,一个文件就对应一个struct内核对象,而进程启动时,会默认打开三个输入输出的文件,当我们打开了好几个文件,创建这些文件的struct file,然后为了方便管理,struct file里面包含一个struct file*的指针,通过这个指针就可以把这些文件以链表的形式进行管理起来,文件链表的形式。每创建一个文件,就链入该链表中进行管理,所以这样把对文件的管理,转换成对链表的增删查改,

而Linux内核以软件的一贯特性,进程管理和文件管理,这是两个单元的,他们两个不能是强耦合,必须是松耦合的!!!

所以我们现在就是要把进程和文件进行关联起来,我们进程是一个结构体对象,文件也是一个结构体对象,这就可以看成结构体与结构体直接的联系,一般情况下一个进程可以打开多个文件,1:n的形式

所以接下来,在我们task_struct中存在一个struct file_struct *files的结构,所指向的struct file_strct结构体对象里面存在一个很重要的成员变量struct file* fd_array[]。包含了一个指针数组。天然就具备下标0 1 2 3.....。然后把键盘文件,显示器文件,显示器文件填入进来,0 1 2就被他们三个给占用了后来我们调用接口open,打开文件,然后我们系统在查的时候在这个数组里面查0 1 2都被占用,3没有被占,然后加载打开这个文件,在内核中把这个struct file创建出来,把该结构体对象的地址填入到3号下标位置。

所以writr(3,message,strlen(message)),就是进程拿着3号描述符找到自己进程内部的

struct file_struct *files,然后找到struct file *fd_arryay[]然后找到3号下标对应的文件进行写入。
这个表叫做文件描述符表,这个表是构建了进程和文件之间的关系的一张表,每一个进程都有这一张表,双方通过指针来完成各个模块的关联和解耦!!!!

我们来打开内核源代码来看看到底是不是这回事

所以fd到底是什么?-----数组下标!!!

在系统里面 ,fd文件描述符是访问文件的唯一方式!!!

可是我们在C语言里用的全是文件流FILE *,而不是fd文件描述符?

所以什么是FILE?什么是FILE*?

这个是C语言给我们提供访问文件的一个东西,因为只能通过文件描述符进行访问,这个FILE是在C语言上封装的一个struct FILE,结构体对象,这个结构体里必定要有很多的属性,但这个属性里面肯定有一个对文件描述符进行封装的一个成员变量!!!

我们来看一下下面代码来进行验证:因为stdin,stdout,stderr是FILE*类型,所以用他进行指向一个成员变量_fileno,就可以看到文件描述符:

cpp 复制代码
int main()
{

    printf("stdin:%d\n",stdin->_fileno);
    printf("stdout:%d\n",stdout->_fileno);
    printf("stderr:%d\n",stderr->_fileno);
    return 0;
}

再来fopen打开一个文件:

cpp 复制代码
int main()
{

    printf("stdin:%d\n",stdin->_fileno);
    printf("stdout:%d\n",stdout->_fileno);
    printf("stderr:%d\n",stderr->_fileno);
    FILE *fp = fopen("log.txt","w");
    printf("fp:%d\n",fp->_fileno);

    return 0;
}

这样就验证上述说法!!!

所以任你文件怎么办,OS只认文件描述符!!!

在C语言上,用到的函数都是对系统调用的封装!!!

不仅做了接口上的封装,还做了类型上的封装就是struct_file!!!

重新理解一切皆文件:

如果理解硬件为文件呢?

OS被称为软硬件的管理者,我们OS系统不光对软件进行管理,也要对硬件进行管理,先描述再组织,所以我们硬件也一定要是一个结构体。

在内核中肯定要有描述这些设备,每一个设备都有一个对应的strut device结构体对象,然后OS通过链表对这些结构体进行管理起来,对设备的管理就变成 对链表的增删查改,对于外设,属性类别一样,但是值可以不一样,对于这些外设最核心的动作就是IO,读写,每一种设备都要有查看内容的放入,最核心的方法就是读和写一个read方法和write方法。

输出一个结论,各个设备的属性可以统一设置成结构体,来体现设备的差异,但方法同样也是不一样的,虽然都叫读写。

对于外设有读写方法,比如键盘读写,显然读写方法没有,就可以把写方法设置为空。

所有的方法在底层实现上肯定是不一样的。

虽然他们方法底层实现不一样,所以Linux设计者,设计,当我们OS启动的时候,创建我们

struct file对象,我们struct file里面有,C语言结构体里面不能包含方法,但是可以包含函数指针:

我们可以让读指针指向底层的方法,写指针指向底层的方法,我们所对应的设备,在在内核中都创建对应的struct file,然后每个对应设备的struct file里面的函数指针指向对应的方法,站在struct file角度,我们要进行读写,我们压根就不需要关系底层实现方法,只要知道函数指针,就能找到底层方法!!!向上我们要访问任何一个硬件,统一叫做读和写等方法,不用关注底层实现,只需要调这个方法,他自动给我们找到底层实现方法。
struct file就想当于在软件层面进行了一次封装,在上层看来,一切皆文件,对上层来说只需要提供struct file就可以。只要找到struct file就可以找到底层的方法,

这套机制在Linux中被叫做VFS(virtual file system)(虚拟文件系统)。

这样在上层就看不到各个设备的差异了,所以一切皆文件!!!

所有用户的行为都会被转换成进程,无论启动读写等各种命令,所有行为都是进程,而我们站在进程角度,只需要拿文件描述符,找到对应的struct file,剩下的就不是进程的事情了,只要找到执行file中的方法,就可以完成对这些设备的操作。

所以进程角度,一起皆文件。

所以之前用的系统调用函数open,read,write,访问键盘显示器,都是调用底层设备对应

struct file中的函数指针对应的方法进行访问。

在上层一切皆文件,底层有各种设备,这种语系在C++当中,这种技术叫做多态!!!这也是C语言实现多态的方法!!!

补充:底层实现的这些方法全是在驱动程序里面!!!

打开内核源代码看看:

文本写入VS二进程写入

在计算机里,OS系统层面上只有二进制概念,语言层看起来可以文本写入,也可以二进制写入。

为什么我们语言喜欢做封装?(C/C++)

我们向显示器写12345,我写的是12345这个整数呢,还是'1''2''3''4''5'字符?-----字符!

我们显示器我们给他叫做字符设备,有一个东西叫做ACSLL码表,写12345,实际上是这几个字符ACSLL码值二进制,这个二进程被我们显示器解释成12345字符

看下面两段代码:

cpp 复制代码
int main()
{
    char *message = "hello\n";
    write(1,message,strlen(message));
    return 0;
}
cpp 复制代码
int main()
{
    int a = 12345;
    write(1,&a,sizeof(a));
    printf("\n");
    return 0;
}

把字符显示到显示器上,直接就是字符,把数字输入到显示器上,显示器是字符设备只认字符,只不过这样的字符转成我们对应的二进程,被显示器转换成字符'9''0'。

所以我们在显示到显示器之前,我们给转换成字符,然后再进行显示,如下代码所示:

cpp 复制代码
int main()
{
    int a = 12345;
    char buffer[1024];
    snprintf(buffer,strlen(buffer),"%d",a);
    write(1,buffer,strlen(buffer));
    printf("\n");
    return 0;
}

这样才能把12345显示到显示器上 ,所以用系统调用是没办法直接把一个整数答应到显示器上,必须进行相关的转换,

所以我们为什么要有printf相关这样的函数呢?不是有write这样的接口呢?、

因为我们再很多的情况,我们需要把内存级的二进制数据转换成字符风格,通过write打印在显示器上,这个过程叫做格式化的过程。

如果在系统中只有系统调用,那么这个格式化的过程必须要我们手动自己设置,然后才能显示。

所以C语言提供一些printf,scanf等一系列接口函数,直接可以进行使用打印,因为在底层对格式化的过程进行了封装!!!

所以为什么我们C语言要给我们很多接口做封装?

1.方便用户进行操作

2.提高语言的可移植性

系统调用的接口类型设计成void*,所以 传过来字符串或者其他类型,你以为你传的是这些类型,在这些接口看来是二进制,因为是void*。

读到的数据,写到的数据,做转化,都是用户进行操作,然后C语言对其进行封装,用户就能直接使用。

为什么喜欢做封装?如果不是Linux平台,而是win或者macos平台?

如果是系统调用接口,那么换平台就不能运行的,如果是封装的接口,在Linux调Linux的系统调用接口,在win调win的接口,在什么平台就调什么平台的接口。

所以语言层,把我们与平台强相关的接口操作做封装,提高语言的可移植性
在使用这些接口的时候,win和Linux或者其他平台提前给我们安装了一些东西,这个东西叫glibc的库,语言层使用的一些接口,在glibc中进行了封装,把库编成Linux版本的库,win版本的库等等

语言的可移植性性越高,这个语言就越流通!!!!

相关推荐
qq_243050793 小时前
dnsmap:DNS枚举 详细完整教程 Kali Linux&Termux入门教程 黑客渗透测试 信息收集
linux·网络·安全·web安全·网络安全·系统安全·ddos
芒果爱编程3 小时前
MCU、ARM体系结构,单片机基础,单片机操作
开发语言·网络·c++·tcp/ip·算法
码字哥4 小时前
EasyExcel设置表头上面的那种大标题(前端传递来的大标题)
java·服务器·前端
m0_748257185 小时前
海康威视摄像头RTSP使用nginx推流到服务器直播教程
运维·服务器·nginx
Pou光明6 小时前
1_linux系统网络性能如何优化——几种开源网络协议栈比较
linux·运维·网络·网络协议·开源
工业甲酰苯胺6 小时前
C语言之输入输出
c语言·c++·算法
C++忠实粉丝7 小时前
计算机网络之NAT、代理服务、内网穿透、内网打洞
网络·c++·网络协议·计算机网络·http·智能路由器
TianyaOAO7 小时前
inmp+discuz论坛
linux·运维·服务器
星光璀璨山河无恙7 小时前
【Linux】grep命令
大数据·linux
寒月6587 小时前
黑盒白盒测试
运维·服务器