基础IO
文章目录
一:c语言中文件的IO操作
【打开文件的上限】
一直打开文件却未关闭,则当打开文件数目到达一定数目就不能再打开文件了,当然这个数目是可以配置的
- 查看配置选项
ulimit -a
- 配置可以打开的文件大小
ulimit -n 2048
- 默认为1024,现在改成了2048
功能 | 表达 |
---|---|
读文件 | r |
写文件 | w |
写操作
#include <stdio.h
#include <string.h>
int main()
{
FILE *fp = fopen("myfile", "w");
//myfile为目标文件,w为写操作
if(!fp){
printf("fopen error!\n");
}
const char *msg = "hello bit!\n"; //书写的内容
int count = 5;
while(count--){
fwrite(msg, strlen(msg), 1, fp);
//size_t fwrite(const void *ptr, size_t size, size_t nmemb,
//FILE *stream);
}
fclose(fp);
//关闭文件流
return 0;
}
读操作
#include <stdio.h>
#include <string.h>
int main()
{
FILE *fp = fopen("myfile", "r");
if(!fp){
printf("fopen error!\n");
}
char buf[1024];
const char *msg = "hello bit!\n";
while(1){
//注意返回值和参数,此处有坑,仔细查看man手册关于该函数的说明
ssize_t s = fread(buf, 1, strlen(msg), fp);
if(s > 0){
buf[s] = 0;
printf("%s", buf);
}
if(feof(fp)){
break;
}
}
fclose(fp);
return 0;
}
【总结】
C默认会打开三个输入输出流,分别是stdin, stdout, stderr
仔细观察发现,这三个流的类型都是FILE*, fopen返回值类型,文件指针
二:系统IO
fread/fwrite/fopen/fclose 库函数
read/write/open/close系统调用(Linux 提供的比较底层的文件操作)
1.写操作
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
#include<stdio.h>
int main(){
int fd=open("./myfile.txt",O_WRONLY);
//int open(const char *pathname, int flags);
//函数原型,对文件有只写权限
if(fd<0){
perror("open");
return 1;
}
char buf[1024]={"将军的荣耀\n"};
//用于存储写的内容
ssize_t n=write(fd,buf,strlen(buf));
buf[n]='\0';//防止越界
printf("%d\n",n);
printf("%s",buf);
close(fd);
return 0;
}
2.读操作
#include<stdio.h>
#include<string.h>
#include<fcntl.h>
#include<unistd.h>
int main(){
int fd=open("./myfile.txt",O_RDONLY);
if(fd<0){
perror("open");
return 1;
}
char buf[1024]={0};
ssize_t n=read(fd,buf,sizeof(buf)-1);
buf[n]='\0';
printf("%s",buf);
close(fd);
return 0;
}
open:【man手册中的介绍】
#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
【总结】 -
fopen fclose fread fwrite 都是C标准库当中的函数,我们称之为库函数(libc)。
-
open close read write lseek 都属于系统提供的接口,称之为系统调用接口
-
系统调用和库函数
三:文件描述符fd(文件描述符就是一个小整数)
0&1&2
- 文件描述符就是从0开始的小整数,当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件.于是就有了file结构体.表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来.每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上文件描述符就是该数组的下标
Linux进程默认打开的三个文件描述符
0:标准输入
1:标准输出
2:标准错误
0,1,2对应的物理设备一般是:键盘,显示器
文件描述符的分配规则
- 每次打开一个文件的时候,会从文件描述符表的开始位置依次往后找,找到第一个空闲的下标位置,就用这个下标来表示新的文件
【对标准输入/输出/错误的文件描述符的验证】
#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
int main(){
//打印标准输入/输出/错误的文件描述符
printf("stdin:%d\n",stdin->_fileno);
printf("stdout:%d\n",stdout->_fileno);
printf("sterr:%d\n",stderr->_fileno);
//open的返回值叫做 文件描述符
int fd=open("./open.txt",O_RDONLY);
//参数1:文件名
printf("fd=%d\n",fd);
return 0;
}
四:重定向
来段代码
#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
int main(){
//重定向:将打印到显示器上的内容输入到文件中这种操作依赖系统操作的底层行为
close(1); //关闭了标准输出的文件描述符不能按常规输出了
int fd = open("test.txt",O_WRONLY);
//第二个参数决定了打开的方式
fprintf(stderr,"fd=%d\n",stdout->_fileno);
//显示器中打印出来,以重定向的方式打开
printf("新的输出:%d\n",stdout->_fileno);
//内容在文件中打印出来
//fprintf(stdout,"新的输出:%d\n",stdout->_fileno);
// stdout->_filen文件描述符
//这条语句的结果在test.txt中输出
return 0;
}
此时我们发现本该出现在文件中的内容出现在了显示器上,
其中fd=1这种现象叫做输出重定向.常见的重定向有:>, >> , <
该程序中关闭了标准输出文件流,重定向的本质为
1.使用dup2()系统调用
【函数介绍】
#include <unistd.h>
int dup2(int oldfd,int newed);
dup2(1,3); //让new成为old的一份拷贝
【注意辨别】
dup2(1,3);
1->old,3->new
dup2(3,1);
3->new,1->old
两者很容易搞混
2.示例代码
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main() {
int fd = open("./log", O_CREAT | O_RDWR);
//有则只读方式打开,没有就创建指定文件
if (fd < 0) {
perror("open");
return 1;
}
close(1);
dup2(fd, 1);
for (;;) {
char buf[1024] = {0};
ssize_t read_size = read(0, buf, sizeof(buf) - 1);
if (read_size < 0) {
perror("read");
break;
}
printf("%s", buf);
fflush(stdout);
}
return 0;
}
3.常见的缓冲区策略
#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
int main(){
printf("hehe\n");
fprintf(stdout,"haha\n");
write(1,"将军的荣耀\n",strlen("将军的荣耀\n"));
fork();
//加了fork函数后直接输出到显示器上./test和./test>out
//,再cat out结果不相同
//fflush(stdout);//手动刷新后两者结果一样
return 0;
}
- printf 与 fwrite(库函数)都输出了2次,而write 只输出了一次(系统调用),为什么呢?
- 一般C库函数写入文件中是全缓冲的,而显示器是行缓冲的
- printf fwirite 库函数会自带缓冲区,当发生重定向到普及文件时,数据的缓冲方式由行缓冲变成了全缓冲
- 而我们放在缓冲区中的数据,就不会被立即刷新,甚至fork之后
- 但是进程退出之后,会统一刷新,写入文件中
- 但是fork的时候,父子数据会发生写时拷贝,所以当父进程准备刷新的时候,子进程也就是了同样的一份数据,随既产生两份数据
- write 没有变化,说明没有所谓的缓冲区
- 没缓冲:
- 行缓冲:遇到\n就刷新,或者缓冲区满才刷新,或者手动刷新(打印到显示器)
- 全缓冲:一直到缓冲区满才刷新,或者手动刷新(输出到文件)
- write系统调用,没有缓冲区,直接显示到显示器上
【write 和 printf区别】
无缓冲区 vs 有缓冲区
printf fwrite 库函数会自带缓冲区,而write 系统调用没有带缓冲区.另外,我们这里所说的缓冲区,都是用户级缓冲区.其实为了提升整机性能,OS也会提供相关内核级缓冲区,那这个缓冲区谁提供呢?
- printf fwrite 是库函数
- write 是系统调用,库函数在系统调用的“上层”.
- 是对系统调用的“封装”,但是write 没有缓冲区,而printf fwrite 有.
五:动态库和静态库
1.动态链接库:
- 把一些.c/.cpp 文件编译生成一种特殊的二进制程序,自身不能直接执行,但是可以被其他的可执行程序调用
【用途】:
客户端更新的时候不用更新整个程序,而是更新其中一部分模块.其中的模块就是以动态库的方式组织的
2.静态链接库
- 把一些 .o 文件打包到一起生成一种特殊的二进制文件,自身不能直接执行。但是可以和其他 .c/.cpp 文件编译生成一个新的可执行程序,这个新的可执行程序就可以单独发布了.
【用途:】 发布小程序的时候可以使用 静态库的方式编译生成一个单独的可执行程序并且不依赖其他的库,发布比较方便
//add.c
void Add(int x,int y){
printf("%d\n",(x+y));
}
//test.c
#include<stdio.h>
extern void Add(int x,int y);
int main(){
Add(10,20);
return 0;
}
想要在test.c中能够执行Add函数需要修改makefile文件
静态库的生成
test:test.c libadd.a
gcc $^ -o $@
#编译文件会将两个.c文件进行"合并"
libadd.a:add.c #生成静态库文件
gcc -c add.c -o add.o #
ar -rc libass.a add.o #将libadd.a打包成add.o
#libadd.a文件也是二进制的文件
- 1.把 .c 文件变成 .o 文件
- 2.把若干个 .o 文件打包成 .a 文件
动态库的生成
test:test.c libaddd.so
gcc $^ -o $@
libadd.so:add.c
gcc add.c -shared -fPIC -o libadd.so
ldd:命令查看一个可执行程序依赖了那些动态库
ldd test
命名规则
lib前缀
.a后缀(静态库)
.so后缀(动态)
LD_LIBRARY_PATH 设定这个环境变量提醒系统去哪些目录中查找动态库
C++的第三方库一般都需要通过源码编译生成动态库或者静态库才能使用,但是由于编译环境的差异,编译过程中出现问题的概率极大.