IO进程
零、系统调用
[1] access
- 函数原型:
int access(const char *filenpath, int mode); - 功能:确定文件或文件夹的访问权限,即确定文件的存取方式(只读,只写,可执行,存在),如果指定的存取方式有效,则函数返回0,否则放回-1
- 参数:
- filepath:文件或者文件夹的路径,当前目录直接使用文件或者文件夹名(当该参数为文件时,access函数能使用mode参数所有值,当该参数为文件夹的时候,access函数值能判断文件夹是否存在)
- mode:要判断的模式,有以下这几种
- R_OK:只判断是否有读的权限,在内核中实际上是
#define R_OK 4 - W_OK:只判断是否有写的权限,在内核中实际上是
#define W_OK 2 - X_OK:判断是否有执行权限,
#define X_OK 1 - F_OK:只判断是否存在,
#define F_OK 0
- R_OK:只判断是否有读的权限,在内核中实际上是
- 返回值:如果有mode所指定的权限,就返回0,否则返回-1
一、动态库和静态库的制作
[1]C语言的编译步骤
- 预处理,展开头文件(里面有函数的声明,但是没有实现,实现要链接库,在最后链接实现),宏的替换,去掉注释,但是不会检查语法错误 -E .i
- 头文件都在
/usr/include/目录中,自己定义的头文件放在该目录里面也可以使用尖括号来包含
- 头文件都在
- 编译,将预处理后的文件汇编生成汇编文件,检查语法错误 -S .s
- 汇编,将汇编文件生成目标文件(二进制文件) -c .o
- 链接:C语言需要依赖各种库,所以编译之后还需要把库链接到最终的可执行文件中
[2] 库
使用ldd命令查看x86架构下ELF文件所需要的库
用arm-none-linux-gnueabi-readelf -d ARM架构的可执行程序 命令查看ARM架构的ELF文件所需要的库
-
库是一种二进制形式,通常把一些仓用的函数制作成各种函数库,然后被系统载入内存中运行
-
库存在的意义:库是已经写好的成熟的可以复用的代码,显示每个程序都需要依赖很多基础的底层库
-
库分为两种
- 静态库:在程序编译时链接阶段被链接到目标代码中,运行程序时候将不需要静态库,编译后可执行程序有所增加
- 动态库(共享库):在程序编译时候并不会马上链接到目标代码中,而是在执行阶段才会被程序载入,编译后的可执行程序体积小,但是需要系统动态库存在
-
系统 静态库 动态库 windows *.lib文件*.dll文件linux *.a文件*.so文件
[3] 静态库的制作
使用GNU的 ar 工具来制作静态库。
-
制作静态库命令(假如我们的文件夹里面有main.c 和 fun.c 和 fun.h的文件)
gcc -c fun.c -o fun.o ar -crs libfunction.a fun.o ##中间的名字可以改变 ##静态库以lib开头,以.a结尾,中间的fun才是他的名字-
linux 中的 ar 命令用于创建或者操作静态库,ar 命令参数如下
参数 意义 -r 将objfile文件插入静态库尾或者替换静态库中同名文件 -x 从静态库文件中抽取文件 objfile -t 打印静态库的成员文件列表 -d 从静态库中删除文件 objfile -s 重置静态库文件索引 -v 显示详细信息 -c 创建静态库文件
-
-
然后编译就可以通过 如下命令来编译
gcc main.c -L./ -lfun -Wall ##其中-L选项后面接的是动态库的路径(本文件中生成的库文件自动存在当前目录中,所以使用./),-l选项后面加的是动态库的名字(上面刚刚提到lib和.a都是库的标准格式,只有中间的fun才是它本身的名字) ##最后生成的可执行文件中已经把静态库加上了,所以此时即时删除libfun.a库文件他还是能够执行
[4] 动态库的制作
-
gcc -fpic fun.c -c -o fun.o gcc fun.o -shared -o libfun.so gcc -fPIC -shared add.c -o libadd.so ##可以结合成一条代码 ##fpic表示位置独立的代码,位置独立的代码即位置无关的代码,在可执行程序加载的时候可以放在内存内的任何位置,若不使用该选项则编译后的代码时位置相关的代码,在可执行程序加载时是通过代码拷贝的方式来满足不同的进程的需要,没有实现真正意义上的位置共享 ##shared表示生成动态链接库 -
为了让系统能成功找到自己制作的动态库,需要定位到该动态库的位置,有三种方式
-
把自己制作的动态库拷贝到 /usr/lib 和 /lib 下
-
在环境变量中添加自己制作的库所在的位置(自己没试过)
sudo vim /etc/bash.bashrc ##对所有的用户有效 sudo vim ~/.bashrc ##这样只对当前的用户有效 ##在.bashrc文件最后填上一行 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/linux/file/dongtaiku -
在
/etc/id.so.conf.d/XXX.conf文件中添加自己库的路径
-
-
使用 ldd 命令可以查看可执行文件调用了哪些库
IO
一、标准IO 和 文件 IO
标准IO无法打开设备文件
[1] 标准IO 和 文件 IO
-
标准IO :遵从"ANSI"(美国国际标准协会) c标准。标准IO相当于在系统调用的基础上做封装,可以让用户不在关心使用的操作系统。还在系统调用的基础上增加了缓冲区的机制是IO操作更为高效。
-
文件IO :遵从POSIX标准,文件IO又叫做系统调用,是有内核提供的操作内核的接口函数,它能够实现用户空间和内核空间的切换,操作系统的类型不同,系统调用的函数也是不同的。
-
不同系统下的IO函数
系统 打开创建文件 读 写 关闭 linux open read write close windows _open _read _write _close macOS Open Read Write Close -
他们之间的优缺点
- 标准 IO 可移植性强
- 标准 IO 加入了缓冲区的机制,提高了IO的效率
[2] 系统调用和库函数的区别

- 系统调用:用户空间进程访问内核的接口,把用户从底层的硬件编程中解放,出来极大的提高了系统的安全性,使用户程序具有可移植性,它是操作系统的一部分
- 库函数:库函数为了实现某个功能而封装起来的API集合,提供统一的编程接口,更加便于应用程序的移植,库函数是语言或者应用程序的一部分
- man man(man手册中将系统调用和函数区分开),man 2 是系统调用,man 3 是库函数
[3]文件类型
- 磁盘文件:指的是一组相关数据的有序的集合,通常存储在外部介质上(磁盘),使用时候才调入内存。
- 设备文件:在操作系统中把每一个与主机相连输入输出设备看作文件,把他们输入输出同等与磁盘文件的读和写
- 计算机的存储在物理上是二进制,所以物理上所有的磁盘文件本质上都是一样的,以字节为单位进行顺序存储。
- 从用户的角度逻辑上把文件分为
- 文本文件:基于字符编码的文件,常见的编码ASCII,一般可以用文本编辑器直接打开,里面存的都是ASCII码值,例如存储一个 5678 ,他会存每个字符的ASCII码
- 二进制文件,基于值编码的文件,比如存储一个 5678 ,他会把5678转化为二进制数来存储
三、标准 IO
他有缓冲区,而且是全缓冲,大小是 4096
[1] 打开文件
C语言中用一个指针变量指向一个文件,这个指针称为文件指针
安装 ctags
-
sudo apt-get install ctags ##安装ctags cd /usr/include/ ## 定位到该目录下 ctags -R ##建立索引 -
使用
vi -t FILE ##可以使用 vi -t 来寻找你想要的东西 vim -t FILE ##用VIM也行 ##然后使用ctrl + } 可以进行下一个搜索, ctrl + t返回上一级 ##ctags能够定位到这些内容 #1.用#define定义的宏 #2.枚举型变量的值 #3.函数的定义、原型和声明 #4.名字空间 #5.类型定义 #6.变量(包括定义和声明) #7.类、结构、枚举类型 #8.类、结构和联合中成员变量或函数
[2] 打开文件函数
-
打开文件:
-
函数原型:
FILE * fopen(const char *pathname, const char *mode); -
功能:打开文件
-
返回值:成功返回文件指针FILE *,失败返回NULL,并设置错误码
-
参数:
-
path :文件名(相对路径或者绝对路径(~除外)) 如:"./test.c"
-
mode :打开文件的方式
模式字符串 含义 "r" 以只读方式打开文件,文件指针指向开头,不改变文件内容,如果文件不存在就会报错 "w" 以写入模式打开文件,文件指针指向开头,如果文件存在就清空文件,无则创建 "a" 以追加方式打开文件,如果文件存在定位文件指针到结尾,在现有文件末尾添加内容,无则创建 "r+" 以读写方式打开文件,文件指针指向开头,如果不存在就会报错,他会从头开始写入,回覆盖原来的内容,要配合随机访问函数fseek "w+" 以读写方式打开文件,如果文件存在,就清空文件,如果不存在就创建一个新文件,文件指针指向开头 "a+" 以读写方式打开文件,如果文件存在定位文件指针到结尾,在现有文件的末尾添加内容,如果文件不存在则创建一个新文件 "rb"、 "wb"、"ab"、"rb+"、"r+b"、 "wb+"、"w+b"、"ab+"、"a+b" 与上一个模式类似,但是以二进制模式而不是文本模式打开文件 "wx"、"wbx"、"w+x"、"wb+x"、"w+bx" 类似于非"x"模式,但是如果文件已存在或以独占模式打开文件,则打开文件失败 - 使用 "w" 模式的时候最好和 "x" 结合使用,防止 "w" 模式直接清空里面原有的内容
-
-
案例
#include <stdio.h> #include <errno.h> // errno #include <string.h> // strerror int main(int argc,const char * agrv) { FILE * fp; fp = fopen("./a.txt","r"); //文件必须存在 if(fp == NULL){ printf("errno = %d\n",errno); printf("msg = %s\n",strerror(errno)); return -errno; } printf("open file success\n"); return 0; }
-
[3] perror() 函数
#include <stdio.h>
功能:打印错误信息
void perror(const char *s); // perror("fopen 出错了! -> ");
参数 s 自己想要打印出的提示信息字符串
[4] 文件关闭 fclose
-
文件关闭:
- 函数原型:
int fclose(FILE *fp); - 功能:关闭文件指针
- 参数:fp:文件指针
- 返回值:成功返回0,失败返回EOF(-1)同时置位errno
- 函数原型:
-
一般用法如下
if (fclose(fp) != 0) { perror("fclose error."); return -1; }
[6] 读写文件 fgetc fputc
-
fgetc函数
- 函数原型:
int fgetc(FILE *stream); - 功能:从流指针中读取一个字符
- 参数:stream:文件指针
- 返回值:成功返回读取到字符的ascii,失败或者错误返回EOF(-1)
- 函数原型:
-
fputc 函数
- 函数原型:
int fputc(int c, FILE *stream); - 功能:向流指针指针中写入一个字符
- 参数:
- c :被操作的字符
- stream:文件指针
- 返回值:成功返回写入字符的ascii,失败或者错误返回EOF(-1)
- 函数原型:
-
练习:使用fgetc统计一个文件中的字符的个数和行号,结果使用wc 命令验证
#include <stdio.h> int main(int argc, const char *argv[]) { int line=0,num=0; int ch; FILE * fp = NULL; if(argc != 2){ printf("input argv error\n"); printf("usage:./a.out filename\n"); return -1; } while((ch = fgetc(fp)) != -1){ num++; if(ch == '\n'){ line++; } } printf("line = %d,num = %d\n",line,num); fclose(fp); return 0; }
[7] 缓冲区
ANSI C采用缓冲文件系统处理数据文件。
-
缓冲区分类
-
全缓存 :用fopen打开的流指针就是全缓冲
- 大小:4096
- 以下这些情况会刷新缓冲区
- 当缓冲区写满4096的时候
- 主动刷新fflush()
- 在程序中调用fclose(fp)的时候
- 程序正常结束
-
行缓存:stdin,stdout 都是行缓冲
-
大小:1024
-
以下这些情况会刷新缓冲区
- 当缓冲区写满4096的时候
- 主动刷新fflush()
- 在程序中调用fclose(fp)的时候
- 程序正常结束
- 遇到\n
-
测试:
#include <stdio.h> #include <unistd.h> int main(int argc, const char *argv[]) { int i=0; while(i<1024){ printf("a"); // 此时刚好缓冲区满了,不打印 i++; } sleep(5); printf("a"); // 此时刷新缓冲区 return 0; }
-
-
无缓存:stderr 就是无缓冲 prror() 函数也是无缓冲
- 大小:1
-
-
fflush 函数
- 函数原型:
int fflush(FILE *stream); - 功能:刷新缓冲区
- 参数:stream:流指针
- 返回值:成功返回0,失败返回EOF,并置位errno。
- 函数原型:
[8] 按照行读写文件 fputs fgets
-
fgets 函数
-
函数原型:
char *fgets(char *s, int size, FILE *stream); -
功能:从流指针中向s地址中输入size个字符
-
参数:
- s:保存输入字符串的首地址
- size:数组字符的个数
- fgets读取size-1个字符,在最后的位置上补充上'\0'
- 第二个参数是读入字符的最大数量。如果是n则只能读入n-1个字符,或者遇到第一个换行符为止,如果超过n个字符,它会截断,然后在数组最后一个元素填充
'\0',最后输出也就是 n-1 个元素
- @stream:流指针
-
返回值:成功返回s的首地址,失败或者读取结束返回NULL,错误原因在errno中
-
案例
#include <stdio.h> #include <string.h> #define N 1024 int main(int argc, const char *argv[]) { char buf[N] = {0}; fgets(buf,N,stdin); buf[strlen(buf)-1] = '\0'; //将终端上输入的'\n'清零 s_gets函数 printf("buf = %s\n",buf); return 0; }
-
-
fputs 函数
- 函数原型:
int fputs(const char *s, FILE *stream); - 功能:将s指向的字符串输出到流中
- 参数:
- s:字符串的首地址
- stream:流指针
- 返回值:成功返回非负的整数,错误返回EOF
- 函数原型:
[9] fprintf/sprintf/snprintf函数的使用
-
fprintf
- 函数原型:
int fprintf(FILE *stream, const char *format, ...); - 功能:将字符串格式化输出到stream流指针中
- 参数:
- @stream:流指针
- @format:通过%s %d等格式化的数据
- 返回值:成功返回格式化的字符的个数,失败返回负数
- 函数原型:
-
sprintf
-
函数原型:
int sprintf(char *str, const char *format, ...); -
功能:将格式化的字符串放到str指向的地址中
-
参数:
- @str:存放字符串的内存的首地址
- @format:格式化字符的方式
-
返回值:成功返回格式化的字符的个数,失败返回负数
-
案例:
#include <stdio.h> #define N 1024 int main(int argc, const char *argv[]) { char buf[N] = {0}; sprintf(buf,"%4d年%02d月%02d日 %02d:%02d:%02d",2020,11,9,15,45,20); printf("buf = %s\n",buf); return 0; }
-
[10] 按照块读写文件 fread 和 fwrite
-
fread
- 函数原型:
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); - 功能:从流指针中读取数据并写入到ptr指向的内存中
- 参数:
- ptr:内存的首地址
- size:单个成员的大小(字节)
- nmemb:成员的个数
- stream:流指针
- 返回值:实际读出的数量
- 函数原型:
-
fwrite
- 函数原型:
size_t fwrite(const void *ptr, size_t size, size_t nmemb,FIEL * stream - 功能:将ptr内存中的数据写入到stream指针对应的文件中
- 参数:
- @ptr:内存的首地址
- @size:单个成员的大小(字节
- @nmemb:成员的个数
- @stream:流指针
- 返回值:成功返回nmemb,读取到文件尾部或失败,nmemb小于想读取的值或0
- 函数原型:
-
练习1:使用fread和fwrite完整文件复制功能
#include <stdio.h> #define N 1024 char buf[N] = {0}; int n = 0; int main(int argc, const char *argv[]) { FILE *sfp,*dfp; if(argc != 3){ fprintf(stderr,"usage: ./a.out srcfile destfile\n"); return -1; } if((sfp = fopen(argv[1],"r")) == NULL){ perror("fopen srcfile"); return -1; } if((dfp = fopen(argv[2],"w")) == NULL){ perror("fopen destfile"); return -1; } while((n = fread(buf,1,N,sfp)) != 0){ fwrite(buf,1,n,dfp); } fclose(sfp); fclose(dfp); return 0; }
[11] ftell/fseek/rewind函数
- fseek
- 函数原型:
int fseek(FILE *stream, long offset, int whence);、 - 功能:定位流指针
- 参数:
- stream 流指针
- offset 偏移(可以是正也可以是负)
- whence:相对位置
- SEEK_SET 文件开头
- SEEK_CUR 光标的当前位置
- SEEK_END 文件的结尾
- 返回值:成功返回0,失败返回-1,更新errno
- 函数原型:
- ftell
- 函数原型:
long ftell(FILE *stream); - 功能:获取光标的当前位置(不会改变光标)
- 参数:stream :流指针
- 返回值:成功返回相对于文件开头的偏移值,失败返回-1,置位errno
- ftell 和 fseek 结合可以计算文件大小
- 函数原型:
- rewind
- 函数原型:
void rewind(FILE *stream); - 功能:将光标归位到文件开头(效果类似fseek(fp,0,SEEK_SET))
- 参数:stream:流指针
- 返回值:无
- 函数原型:
[12] 获取系统时间
需要头文件 #include <time.h>
-
time
-
头文件:
#include <time.h> -
函数原型:
time_t time(time_t *t); -
功能:获取从1970-01-01 00:00:00到目前为止的seconds(这是一个非常大的值)
-
参数:t:获取到的变量的地址 (一般填写 NULL)
-
返回值:成功返回秒数,错误返回(time_t -1),同时设置errno
-
-
localtime(const time_t *timep);-
功能:将秒钟数转化到tm结构体中
-
tm结构体
struct tm { int tm_sec; /* seconds 0-59*/ int tm_min; /* minutes 0-59*/ int tm_hour; /* hours 0-23*/ int tm_mday; /* day of the month 1-31*/ int tm_mon; /* month 0-11*/ int tm_year; /* year 1900*/ int tm_wday; /* day of the week 0-6*/ int tm_yday; /* day in the year,第多少天0-365 */ int tm_isdst; /* daylight saving time */ };
-
-
参数:timep,秒钟数,一般都是填写time函数的返回值
-
返回值:成功返回tm结构体,失败返回NULL,并置位errno
-
-
示例代码
#include <stdio.h> #include <time.h> int main(int argc, const char *argv[]) { time_t tim; struct tm *tm; tim = time(NULL); if(tim == -1){ fprintf(stderr,"get time error\n"); return -1; } tm = localtime(&tim); if(tm == NULL){ fprintf(stderr,"change time error\n"); return -1; } printf("%4d-%02d-%02d %02d:%02d:%02d week:%d\n", 1900+tm->tm_year,tm->tm_mon+1,tm->tm_mday, tm->tm_hour,tm->tm_min,tm->tm_sec,tm->tm_wday); return 0; }

浙公网安备 33010602011771号