Linux系统编程笔记(李慧琴)
01标准IO介绍
I/O:input & output, 是一切实现的基础,主要分为
- stdio 标准IO (所有的标准都是为了和稀泥整合不同)
- sysio 系统调用IO
注意:标准库函数都在man手册的第三章,如果man手册不了解的话,可以先man man
, 将man手册的使用搞清楚。
-
1.stdio中的函数 (
-
文件的操作:
- fopen();
- fclose();
-
字符的操作:
- fgetc();
- fputc();
-
字符串的操作:
- fgets();
- fputs();
-
二进制数据块操作:
- fread();
- fwrite();
-
printf();一族的函数
-
scanf();一族的函数
-
fseek();
-
ftell();
-
rewind();
-
fflush();
FILE
类型贯穿始终)
man的用法
-
man手册第三章是标准的库函数, 例如
man fopen
, 会自动跳到第三章
-
第二章是系统调用
-
第一章是基本的命令, 例如
man 1 ls
-
对于开发者来说,最重要的是第七章,第七章讲的是机制,比如不明白什么是tcp, 可以用
man 7 tcp
来解释什么是TCP。man epoll
机制等
fopen()函数
man文档描述:
mode参数指向一个字符串,但是只取字符串的第一个字符,所以可以写成mode="write"
,也可以写成mode="w"
, 他们的效果是一样的。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main()
{
FILE *fp;
fp = fopen("tmp", "r");
if(fp == NULL)
{
fprintf(stderr, "fopen() failed! errno=%d\n", errno);
perror("fopen() 函数"); //推荐使用, 可以自动关联全局变量errno
fprintf("fopen 失败了, 失败原因是:%s", strerror(errno));
exit(1);
}
puts("OK!");
exit(0);
}
FILE指针指向的是哪里的内存?
栈 、 静态区、堆??
FILE *fopen(const char *pathname, const char *mode)
{
return ...; //指针
}
- 如果在栈上,当程序执行到fopen()函数时,创建一个栈上的变量,但是函数结束后,该空间被释放,所以在栈上是不可能的。
- 在栈上保存是不现实的,那么可以保存在静态空间上,但是,函数中的变量放在静态区的时候,如果函数被重复调用,变量在静态区中的内存只有一块,第二次调用的时候第二个文件的结构体会把第一次调用的文件的数据覆盖,所以不适合放在静态区。
- 如果放在堆上, 需要malloc()空间。相对应的free()函数是放在fclose()函数中的。这是成对出现的。
如果一个函数的返回值是指针,同时会有一个逆操作,那么这个函数的指针是放在堆上的。
生成的文件权限
注意:
- 谁打开谁关闭
- 谁申请谁释放
- 是资源就有上限
当前进程中可以打开文件,但是我打开的文件一定是有上限的。下边程序是看进程中最多可以打开多少文件。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main()
{
FILE *fp=NULL;
int count = 0;
while(1)
{
fp = fopen("tmp", "w");
if(fp == NULL)
{
perror("fopen() 函数");
break;
}
count++;
}
printf("count = %d", count);
exit(0);
}
执行结果:
注意: 在不更改当前任何环境的情况下,一个进程打开默认打开了三个流(stream), 分别是: stdin
标准输入、 stdout
标准输出、 stderr
标准出错。所以最多可以打开的文件流是1021+3=1024个。
ulimit -a 命令
有一个命令,叫做ulimit -a
这里显示了open file的最大个数是1024.
如果写ulimit -n 1000
这个命令的意思是修改open file的最大数值。
生成文件的权限
如果文件不存在的话,mode=w
权限会创建文件.
生成的文件权限是664, 但是程序中,并没有指定文件的权限。这个文件的权限是怎么来的?
生成的文件的权限是遵循一个公式:0666 & ~umask
umask的值是0002(0开头的是8进制数):转换为二进制数是000 000 010
0666 & ~0002:–>
110 110 110
& ~000 000 010
–>
110 110 110
& 111 111 101
–>
110 110 010
也就是生成的文件的权限664了。
umask这种机制的存在,就是为了防止产生权限过松的文件。 可以看出的是,umask的值越大,被降的值的权限越低,消掉的权限越多。
fgetc() 和 fputc()函数
man一下getchar如下:
getchar()相当于getc(), 默认的输入在stdin中来的。
getc()相当于fgetc()函数。
getc()和fgetc()的区别是??
从原始定义上来讲,这两个函数一个被定义为宏, 一个被定义为函数。
- getc()被定义为宏来使用
- fgetc()被定义为函数来使用
函数和宏的区别:
内核中链表的实现,通篇都是用宏来实现的,没有任何的函数,为什么要这样做? 因为内核的实现是在帮你节约一点一滴的时间,宏不占用你的调用时间,只占用编译时间;函数不占用编译时间,只占用调用时间。
例子
- 文件copy功能
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
int main(int argc, char **argv)
{
FILE *source , *target;
int c;
// 如果用到了命令行参数,需要先判断命令行参数个数
if(argc < 3)
{
fprintf(stderr, "Usage ... \n");
exit(1);
}
source = fopen(argv[1], "r");
if(source == NULL)
{
perror("source file fopen()");
exit(1);
}
target = fopen(argv[2], "w+");
if(target == NULL)
{
perror("target file fopen()");
fclose(source); // 如果不关闭source, 会造成内存泄漏
eixt(1);
}
while(1)
{
c = fgetc(source); // 返回值是整形,因为有-1的情况存在
if(c == EOF) break;
fputc(c, target);
}
fclose(source); // 先关闭依赖别人的对象
fclose(target);
exit(0);
}
可以使用diff
命令查看两个文件是否相同:
diff temp temp1
如果命令行没有输出,则说明两个文件内容相同
fgets() 和 fputs()函数
man一下gets()
函数如下:
建议不要使用gets()函数,因为它有Bug, 它不检查缓冲区的溢出,我们可以使用fgets()代替使用gets()。
为什么gets()函数危险??
因为它只约定了一个地址,从终端上接收来的内容(敲一串字符以后回车, 这行字符串在输入的时候并没有放在指定的地址里面,而是放在了当前的输入缓冲区中,当回车的时候,这串内容才被放到指定的地方去。)
gets()函数不检查缓冲区溢出,这里的缓冲区说的是输入缓冲区,而不是程序的缓冲区
fgets()
fgets()函数的定义:
char *fgets(char *s, int size, FILE *stream);
size
: 指定的接收大小stream
: 指定的流*s
: 放入的内存地址
fgets()的结束有两种情况:
- 读到了size-1个字节(最后一个字节留给补全的字符串尾名
\0
:\0
表示当前串的结束) - 读到了
\n
字符
fread() 和 fwrite()函数
注意: linux环境下不区分二进制流和文本流
网络套接字是文件IO操作
网络套接字抽象出来,是一个文件IO操作,是一个文件描述符。
FILE *fdopen(int fd, const char *mode);
这个函数是使用已有的文件描述符来指定打开方式,从而把它封装成一个流的操作。换句话说,你的套接字SOCKET返回给你的文件描述符,但是摇身一变,这个文件描述符就能封装到一个流当中。你对一个stream的读写,实际上就是相当于在读写我网络套接字。
Unix的一句话:一切皆文件 , 文件是所有实现的基础,包括一些设备,管道全部抽象的是IO操作
printf()族函数
man 3 printf
可以看到printf()一族的函数
int printf(const char *format, ...);
printf()
的函数的功能是,将一些函数按照一定的格式输出到stdout上。
建议使用fprintf()
,因为当前的流该往哪输出就往哪走向,不建议将所有的输出都放在stdout上,因为有的时候当命令行的传参不正确的时候,(默认情况下,stdin是指向键盘的,stdout和stderr是指向显示器),我们可以根据自己的需要改变输出位置(把输入输出做重定向),例如,stderr指向一个打开的文件。
一般来说会把stderr输出到一个文件里,因为会发生例如:在跑内核或者大程序的时候,跑的过程当中会出现一些警告,还有一些正常的输出的内容,我们有的时候会需要把这两者分开,把输入重定向到一个文件当中,把标准出错重定向到一个文件当中。
sprintf()
int sprintf(char *str, const char *format, ...);
这个函数的功能是把format的内容,输出到一个字符串当中去。
**atoi()**函数,把一个串转换成一个整形数。
#include <stdio.h>
#include <stdlib.h>
int main()
{
char str[] = "123456"; // 如果字符串中间有字符的话,就拿到字母为止。
printf("%d \n", atoi(str));
exit(0);
}
如果我需要一个将多个不同的数据类型综合成一个串, 使用sprintf()
,例如:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int year = 2020, month=12, day=11;
// 想输出2020-12-11
char buf[1024];
sprintf(buf, "%d-%d-%d", year, month, day);
puts(buf);
exit(0);
}
scanf()一族函数
注意 :在scanf()一族的函数中,一定要慎重使用%s
, 因为在终端上输入的时候,你是不清楚这个字符串有多长的,所以看不到目标位置有多大,这是scanf的缺陷之一。
fseek() ftell() 和 rewind()文件位置函数
这三个函数是用来操作文件位置指针的。
fopen()
函数由于打开方式的不同,文件位指针的位置不同,有的在文件首,有的在文件尾。
文件种有个文件位指针,在打开文件进行读写的时候,文件位指针所在的位置称为当前位置。不是每次用的时候都在文件首的位置。所以三个函数是用来解决以上的问题的。
fseek() 定位
int fseek(FILE *stream, long offset, int whence);
- offset: 偏移量大小
- whence: 偏移的相对位置(包含
SEEK_SET
文件首,SEEK_CUR
文件当前位置, orSEEK_END
文件尾,)
ftell()
ftell()
用来显示文件指针当前位置在哪,通常和fseek()
一起使用。
例子 :计算文件有效字符的数量
#include <stdio.h>
int main(int argc, char **argv)
{
FILE *fp;
if(argc < 2)
{
fprintf(stderr, "Usage...");
exit(1);
}
fp = fopen(argv[1], "r");
if(fp == NULL)
{
perror("fopen()");
exit(1);
}
fseek(fp, 0, SEEK_END); // 将文件位指针指向文件尾
printf("count = %ld", ftell(fp)); // 打印当前文件指针位置
fclose(fp);
exit(0);
}
rewind()
rewind()
将文件指针定位到文件首位置,相当于fseek(fp, 0, SEEK_SET);
空洞文件
fseek()
函数经常用来帮助我们完成一个空洞文件(空洞文件种,全部或者一部分位置充斥着字符\0
, ASCII码为0的字符)。
-
空洞文件的用途:
-
下载工具:如果下载一个2G的文件,建立下载任务之后,在磁盘上会马上产生一个文件,这个文件不是慢慢涨为2G的文件,而是直接产生的时候就是2G的大小。因为这样不会产生下载到一半的时候产生空间不足的问题。这个文件就是空洞文件,因为要先占上磁盘,下载文件产生之后马上调用
fseek()
, 将文件指针指向文件尾。这2G空间中全部为
\0
的字符。
下载工具会把空洞文件切成片,用多线程来进行每一小块的数据下载。
fflush()函数
例子
#include <stdio.h>
int main()
{
printf("Before while()");
while(1);
printf("After while()");
exit(0);
}
我们认为当这个程序执行的时候,"Before while()"
会被打印,但是"After while()"
不会被打印。但是实际执行的时候"Before while()"
也不会被打印,为什么???
因为printf()
是典型的行缓冲模式,只有遇到换行的时候,才会刷新缓冲区,或者一行满了来刷新缓冲区。所以不会打印。
所以强调如果没有特殊要求,要在printf()
末尾加上\n
。
还有一种方法是使用fflush()
强制刷新缓冲区。如果有多个流同时打开,调用fflush(NULL)
时,会将多个流都可以 强制刷新。
缓冲区
-
缓冲区的作用 :
缓冲区的存在大多数情况下时好事,用来合并系统调用。 -
主要分为:
- 行缓冲: 换行的时候刷新,满了的时候刷新, 强制刷新(fflush,标准输出是这样的,因为是终端设备)
- 全缓冲: 满了的时候刷新,强制刷新(默认, 只要不是终端设备)
- 无缓冲:如stderr,需要立即输出的内容
技巧: VIM视窗模式下, 光标停留在某个函数上,然后按shift + k
就会跳转到这个函数的man
手册中去。然后按两次q
会回到原来的文件中去。
getline() 函数 获得一行
以上讲的函数并没有一个函数可以取得一行的数据。如何解决这个问题???
(动态内存实现), 先要一块内存,如果这块内存不够了再继续要,一直要到满足内存大小为止。这个功能可以借助malloc
和realloc
一系列动态内存函数实现。
getline()
函数实际上是将上面说的内容封装了一遍。
临时文件
临时数据放在临时文件中,
- 如何创建临时文件才能不去冲突???
- 忘记及时销毁??
有两个常用函数tmpnam
和 tmpfile
- tmpnam
tmpnam
创建临时文件是比较危险的,因为它创建临时文件需要两步,而当前的机器中,并不能做到绝对的一次性执行这两步(产生文件马上创建文件)。所以没有办法创建一个非常安全的临时文件。 - tmpfile
tmpfile
以二进制读写的形式打开一个临时文件,它产生的临时文件有一个特点:对于一个程序员来说并不关心临时文件的文件名是什么,而tmpfile
函数会产生一个匿名文件(实际上磁盘上已经创建了这个文件, 但是我们并不知道这个文件存在哪,这种文件称为匿名文件),然后tmpfile
返回给一个指向这个文件的文件指针。
一个文件如果说没有任何的硬链接指向它,而当前的文件的打开计数又已经成为0,那么这块数据就要被释放了,所以不用考虑第二个及时销毁的问题。
下一章 《系统调用IO》
今天的文章linux编程基础李养群_vim实用技巧第2版pdf分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/61957.html