前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >基础I/O--重定向&&缓冲区&&stderr

基础I/O--重定向&&缓冲区&&stderr

作者头像
南桥
发布2024-05-26 15:24:16
670
发布2024-05-26 15:24:16
举报
文章被收录于专栏:南桥谈编程南桥谈编程

概述篇

常见的重定向有:>, >>, <

先来看一串代码:

代码语言:javascript
复制
#include<sys/types.h>    
#include<sys/stat.h>    
#include<fcntl.h>    
#include<unistd.h>    
#include<string.h>    
    
const char *filename = "log.txt";    
    
int main()    
{    
  close(1);    
  int fd=open(filename,O_CREAT|O_WRONLY|O_TRUNC,0666);    
  if(fd<0)    
  {    
    perror("open");    
    return 1;    
  }    
  printf("printf,fd: %d\n",fd);    
  fprintf(stdout,"fprintf,fd: %d\n",fd);    
    
  fflush(stdout);    
  close(fd);    
  return 0;                                                                
}    

运行结果:

代码语言:javascript
复制
printf,fd: 1
fprintf,fd: 1

代码解释: 在这段代码中,使用了 open 函数打开文件,并通过 fd 文件描述符来引用该文件。然后,您使用 printffprintf 函数向标准输出写入内容,并使用 fflush 函数刷新标准输出缓冲区,确保内容被写入文件。最后,使用 close 函数关闭文件。

本应该打印在显示器中,但是打印到在指定文件中,这种技术叫做重定向。

重定向原理: 根据文件描述符的分配规则,新打开的这个文件的文件描述符就是 1,即文件描述符表(file*的数组)1 号下标里面存储的就是新打开的文件对象的地址。接下来调用 write 接口,向 1 号文件描述符中进行写入,本来 1 号文件描述符对应的是显示器文件,原本向显示器文件中写入的内容,此时就被写入到新打开的文件中,没有向显示器文件中写入,因此屏幕上就不会出现字符串。

重定向的本质:是在内核中改变文件描述符表特定下标的内容,和上层无关!

为什么需要fflush函数刷新标准输出缓冲区?

每一个系统中新建的文件都会有方法表和内核文件缓冲区。 操作系统上层有系统调用和语言层,这里我们以C语言为例。在语言中层,有stdinstdoutstderr,我们使用的printf/fprintf函数都是调用stdout。在C语言层面对应的struct FILE*对应的结构体除了_fileno还有语言级别的文件缓冲区,在使用printf/fprintf时,并不是通过1号文件操作符将数据直接写到操作系统内部,而是直接写入对应的语言级别的文件缓冲区里,然后C语言通过1号文件操作符刷新到内核文件缓冲区。fflush刷新不是将底层缓冲区刷新到外设上,是把语言级别的缓冲区通过文件操作符写入到内核文件缓冲区中。

理解重定向

代码语言:javascript
复制
#include <unistd.h>

int dup(int oldfd);
int dup2(int oldfd, int newfd);

该函数的作用是将文件描述符 oldfd 复制到文件描述符 newfd 上,并关闭 newfd (如果 newfd 已经被使用则会先关闭)。该函数返回新的文件描述符 newfd 或者在出错时返回-1。最终剩的是oldfd中的内容。

  • 如果 oldfd 不是一个有效的文件描述符,那么调用失败,newfd 不会被关闭。
  • 如果 oldfd 是一个有效的文件描述符,并且newfd 的值与 oldfd 相同,那么 dup2() 不做任何操作,直接返回 newfd

该函数主要用于重定向标准输入、标准输出和标准错误输出。

本质是文件描述符下标所对应的内容的拷贝

如下图所示,新打开的文件对应的文件描述符小标为3,将3拷贝到1号文件描述符中,此时1号不再指向标准输出,而是指向了新打开的文件,3号内容会拷贝到1号。dup2(fd,1)

实例:

代码语言:javascript
复制
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>    
#include<unistd.h>    
#include<string.h>    

const char *filename = "log.txt";    
    
int main()    
{    
  int fd=open(filename,O_CREAT|O_WRONLY|O_TRUNC,0666);    
    
  dup2(fd,1);                                                              
  printf("hello world\n");    
  fprintf(stdout,"hello world\n");    
    
  return 0;    
}    

dup2()函数将文件描述符1(标准输出)复制到文件描述符fd上,因此后续的输出语句都会被重定向到文件log.txt中。如果你运行这段代码,你将看不到hello world的输出在终端上,而是在log.txt文件中。

追加重定向:

代码语言:javascript
复制
#include<stdio.h>    
#include<sys/types.h>    
#include<sys/stat.h>    
#include<fcntl.h>    
#include<unistd.h>    
#include<string.h>    
    
const char *filename = "log.txt";    
    
int main()    
{    
  //int fd=open(filename,O_CREAT|O_WRONLY|O_TRUNC,0666);    
  int fd=open(filename,O_CREAT|O_WRONLY|O_APPEND,0666);                    
    
  dup2(fd,1);    
  printf("hello world\n");    
  fprintf(stdout,"hello world\n");    
    
    
  return 0;    
}   

O_APPEND标志会将数据追加到文件末尾,而不是覆盖现有内容。

缓冲区

概述篇

缓冲区好处:

  1. 解耦,用户将数据交给缓冲区,用户不用管底层如何刷新,这样用户和硬件就解耦了
  2. 提高效率,调用系统调用接口是由时间成本的,因为需要操作系统配合,操作系统是个“大忙人”。因此尽量少调用操作系统,这样效率就高了。例如,由于缓冲区的存在,就提高了使用者使用printffprintf等函数的效率。这样不仅提高使用者的效率,还提高了系统IO的效率

缓冲区:是一段内存空间,给上层提供高效的IO体验,间接提高整体的效率。

缓冲区刷新策略:

  • 刷新策略
  1. 立即刷新(无缓冲),fflush(stdout)fsync(fd)
  2. 行刷新。显示器采用行刷新(照顾用户的查看习惯)
  3. 全缓冲。缓冲区写满才刷新,普通文件采用
  • 特殊情况 进程退出,系统自动刷新(强制)

实践篇

先看一段代码:

代码语言:javascript
复制
#include<stdio.h>    
#include<sys/types.h>    
#include<sys/stat.h>    
#include<fcntl.h>    
#include<unistd.h>    
#include<string.h>    
    
const char *filename = "log.txt";    
    
int main()    
{    
  //C    
  printf("hello printf\n");    
  fprintf(stdout,"hello fprintf\n");    
  const char *msg="hello write\n";    
  //system                                                                 
  write(1,msg,strlen(msg));    
    
  return 0;    
}    

运行结果:

在上述代码添加一个fork()

代码语言:javascript
复制
#include<stdio.h>    
#include<sys/types.h>    
#include<sys/stat.h>    
#include<fcntl.h>    
#include<unistd.h>    
#include<string.h>    
    
const char *filename = "log.txt";    
    
int main()    
{    
  //C    
  printf("hello printf\n");    
  fprintf(stdout,"hello fprintf\n");    
  const char *msg="hello write\n";    
  //system    
  write(1,msg,strlen(msg));    
    
  fork();                                                                  
    
  return 0;    
}    

运行结果:

对比:

./myfile运行代码,是显示器文件,默认的是行刷新, ./myfile > log.txt向普通文件写入,刷新策略发生变化,即全缓冲 如果没有fork,不论重不重定向,最终数据直接写到操作系统内部,但是一旦重定向到文件时,再做写入时,这printffprintf两个文件缓冲区没有写满,因此这两个文件的缓冲区会把你的数据暂时保存起来。但是write调用直接写到系统内核里。当在使用fork时,write数据已经写到操作系统内部,乃至硬件上,但是printffprintf的消息依旧在语言级别的stdout对应的缓冲区中。

封装一下简单库

代码语言:javascript
复制
#pragma once 

#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#include<unistd.h>

#define LINE_SIZE 1024
#define FLUSH_NOW 1
#define FLUSH_LINE 2
#define FLUSH_FULL 4

struct myFILE 
{
  unsigned flags;
  int fileno;
  //缓冲区
  char cache[LINE_SIZE];
  int cap;
  int pos;
};          
myFILE* my_fopen(const char *path,const char *flag)
{
  int flag1=0;
  int iscreate=0;
  mode_t mode=0666;
  if(strcmp(flag,"r")==0)                                                  
  {
    flag1=(O_RDONLY);
  }
  else if(strcmp(flag,"w")==0)
  {
    flag1=(O_WRONLY|O_CREAT|O_TRUNC);
    iscreate=1;
  }
  else if(strcmp(flag,"a")==0)
  {
    flag1=(O_WRONLY|O_CREAT|O_APPEND);
    iscreate=1;
  }
  else 
  {}
   int fd=0;
  if(iscreate)                                                             
    fd=open(path,flag1,mode);
  else 
    fd=open(path,flag1);
  if(fd<0)
    return NULL;
  
  myFILE *fp=(myFILE*)malloc(sizeof(myFILE));
  if(!fp) return NULL;

  fp->fileno=fd;
  fp->flags=FLUSH_LINE;
  
  fp->cap=LINE_SIZE;
  fp->pos=0;

  return fp;

}
void my_fflush(myFILE *fp)
{
 write(fp->fileno,fp->cache,fp->pos);
 fp->pos=0;
}                                                                          

ssize_t my_fwrite(myFILE *fp,const char *data,int len)
{
  memcpy(fp->cache+fp->pos,data,len);
  fp->pos+=len;

  if((fp->flags&FLUSH_LINE) && fp->cache[fp->pos-1]=='\n')
  {
    //write(fp->fileno,fp->cache,fp->pos);
    //fp->pos=0;
    my_fflush(fp);
  }
  
  return len;
}

void my_fclose(myFILE *fp)
{
  //my_fflush(fp);
  close(fp->fileno);
  delete fp;

}

C语言为什么要在FILE中提供用户级缓冲区?为了减少底层调用系统调用的次数,让C语言IO函数效率更高

stderr

我们写的程序,本质上都是对数据进行处理(计算、存储…)。 那么这些数据从哪里来,去哪里,用户要不要看到这个过程。

文件描述符2默认打开是为了方便我们debug

实例:

代码语言:javascript
复制
#include<stdio.h>    
    
int main()    
{    
  fprintf(stdout,"hello fprintf stdout\n");    
  fprintf(stderr,"hello fprintf stderr\n");                                
    
  return 0;    
    
}  

对上述程序进行一个追加,将内容追加到log.txt,就会发现了区别:

>是标准输出重定向,更改1号fd里面的内容,并没有更改2号下标,此时1号往log.txt中写

为什么需要有2号下标?我们在写程序时会有两种情况,正确or错误,不管是正确还是错误,都是往1号fd中打,一旦出错,就往2号下标里面打,这样错误的信息和正确的信息在文件层面上就分开了。

比如我们现在打印一大批消息:

将内容分开:

将1号和2号fd从指向原来的显示器文件分别改成ok.txterr.txt中。

全部放在一个文件中的其他写法:

./a.out 1>all.txt 2>&1是一个命令行的输入,它将程序的标准输出(stdout)重定向到all.txt文件,并将标准错误输出(stderr)也重定向到同一个文件。具体地说,1>表示将stdout重定向到文件all.txt,2>&1表示将stderr重定向到与stdout相同的位置,即all.txt文件。

执行该命令后,程序的所有输出(包括正常输出和错误信息)都将写入到all.txt文件中。

C语言中perror本质是向2里面打印

在C++中,cout相当于C语言中的printfcerr相当于C语言中perro

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-05-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 概述篇
  • 理解重定向
  • 缓冲区
    • 概述篇
      • 实践篇
      • 封装一下简单库
      • stderr
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档