UNIX编程之冲洗内存流与null追加策略(APUE F5-15)
- 博文链接:http://haoyuanliu.github.io/2016/04/29/mysql/
- 对,我是来骗访问量的!O(∩_∩)O~~
最近一直在拜读APUE(Advanced Programming in the UNIX Environment),在读到标准I/O库的时候,对于图 5-15的代码和内存流的写入方式发现冲洗内存流和null字节的追加策略书上没有说明白,到底是如何追加null字节的,调用fclose为什么没有追加null字节等等,对于这一系列问题通过代码测试终于搞清楚整体的写入追加策略,特此记录一下。
解决问题的关键在于书上这一句话:
- 任何时候需要增加流缓冲区中的数据量以及调用fclose、fflush、fseek、fseeko以及fsetpos时都会在当前位置写入一个
null字节!
看似是一旦调用fclose就要追加null,可以书上给的例子可不是这样哦,下面随着文章我们来一步一步地分析。
内存流写入代码
#include "apue.h"
#define BSZ 48
int main()
{
FILE *fp;
char buf[BSZ];
memset(buf, 'a', BSZ-2);
buf[BSZ-2] = '\0';
buf[BSZ-1] = 'X';
if ((fp = fmemopen(buf, BSZ, "w+")) == NULL)
err_sys("fmemopen failed");
printf("Initial buffer contents: %s\n", buf);
fprintf(fp, "hello, world"); //写进缓存
printf("Before flush: %s\n\n", buf);
fflush(fp); //调用fflush、fclose、、fseek、fseeko、fsetpos会在当前位置添加null
printf("After fflush: %s\n", buf);
printf("Len of string in buf = %ld\n\n", (long)strlen(buf));
memset(buf, 'b', BSZ-2);
buf[BSZ-2] = '\0';
buf[BSZ-1] = 'X';
fprintf(fp, "hello, world");
fseek(fp, 0, SEEK_CUR); //保持偏移值冲洗之后的位置
printf("After fseek: %s\n", buf);
printf("Len of string in buf = %ld\n\n", (long)strlen(buf));
memset(buf, 'c', BSZ-2);
buf[BSZ-2] = '\0';
buf[BSZ-1] = 'X';
fprintf(fp, "hello, world"); //继续写进去
fseek(fp, 0, SEEK_SET); //偏移值设为缓冲区开始位置
printf("After fseek: %s\n", buf);
printf("Len of string in buf = %ld\n\n", (long)strlen(buf));
memset(buf, 'd', BSZ-2);
buf[BSZ-2] = '\0';
buf[BSZ-1] = 'X';
fprintf(fp, "hello, world"); //继续写进去
fclose(fp); //然后fclose在当前位置也就是数据尾端添加一个null
printf("After close: %s\n", buf);
printf("Len of string in buf = %ld\n", (long)strlen(buf));
return(0);
}
程序执行的结果为:
Initial buffer contents:
Before flush:
After fflush: hello, world
Len of string in buf = 12
After fseek: bbbbbbbbbbbbhello, world
Len of string in buf = 24
After fseek: cccccccccccccccccccccccchello, world
Len of string in buf = 36
After close: hello, worlddddddddddddddddddddddddddddddddddd
Len of string in buf = 46
写入操作分析
首先是使用a字符修改缓冲区:
memset(buf, 'a', BSZ-2);
buf[BSZ-2] = '\0';
buf[BSZ-1] = 'X';
此时得到的buf应该是:
- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0X
为了识别方便,这里每隔5个字符使用一个+隔开显示,如下所示:- aaaaa + aaaaa + aaaaa + aaaaa + aaaaa + aaaaa + aaaaa + aaaaa + aaaaa + a0X
- 此时偏移值指向数据末尾,即offset = 47
然后执行:
if ((fp = fmemopen(buf, BSZ, "w+")) == NULL)
err_sys("fmemopen failed");
流控制参数为w+,fmemopen函数在缓冲区开始处放置了null字节,此时的buf应该是:
null+ aaaaa + aaaaa + aaaaa + aaaaa + aaaaa + aaaaa + aaaaa + aaaaa + aaaaa + 0X- 因为首字符即为
null,此时打印结果为空;- 此时偏移值指向缓冲区开始
null字节处,即offset = 0
然后通过fprintf函数写入数据,并使用fflush函数冲洗缓冲区:
fprintf(fp, "hello, world"); //写进缓存
fflush(fp); //调用fflush、fclose、、fseek、fseeko、fsetpos会在当前位置添加null
利用fflush函数引起缓冲区冲洗,并在当前位置设置null,此时的buf为:
- hello, world +
null+ aaaaa + aaaaa + aaaaa + aaaaa + aaaaa + aaaaa + aaa0X- 此时的打印结果为: "hello, world"
- 此时的偏移值指向
null,即offset = 12
然后继续执行:
memset(buf, 'b', BSZ-2);
buf[BSZ-2] = '\0';
buf[BSZ-1] = 'X';
用b字符改写缓冲区,此时的buf为:
- bbbbb + bbbbb + bbbbb + bbbbb + bbbbb + bbbbb + bbbbb + bbbbb + bbbbb + b0X
- 此时的偏移值仍旧指向原来的位置,即为offset = 12
然后继续想缓冲中写入数据:
fprintf(fp, "hello, world"); //继续写进
fseek(fp, 0, SEEK_CUR); //保持偏移值在冲洗之后位置
利用fseek函数引起缓冲区冲洗,并在当前位置设置null,此时buf为:
- bbbbb + bbbbb + bb + hello, world +
null+ bbbbb + bbbbb + bbbbb + bbbbb + b0X- 打印结果为: bbbbbbbbbbbbhello, world
- 偏移值位于
null,即offset = 24
然后经过继续写入之后:
memset(buf, 'c', BSZ-2);
buf[BSZ-2] = '\0';
buf[BSZ-1] = 'X';
fprintf(fp, "hello, world"); //继续写进去
fseek(fp, 0, SEEK_SET); //偏移值设为缓冲区开始位置
利用fseek函数引起缓冲区冲洗,并在当前位置设置null,此时的buf为:
- cccccccccccc + cccccccccccc + hello, world +
null+ ccccc + cccc0X- 打印结果为:cccccccccccccccccccccccchello, world
- 此时由于
fseek函数,偏移值设为了缓冲区的开始位置,即offset = 0
最后执行写入,注意此时的偏移值位于缓冲区开始:
memset(buf, 'd', BSZ-2);
buf[BSZ-2] = '\0';
buf[BSZ-1] = 'X';
fprintf(fp, "hello, world"); //继续写进去
fclose(fp); //然后fclose在当前位置也就是数据尾端添加一个null
直接使用fclose函数关闭流,没有追加null,此时的buf为:
- hello, world + null + ddddd + ddddd + ddddd + ddddd + ddddd + ddddd + ddd0X
- 打印结果为 hello, worlddddddddddddddddddddddddddddddddddd
但是前面不是说了一旦调用fclose等函数,就会自动在当前位置写一个null字节嘛, 为什么这里没有追加呢?请看下面三个测试方案
null追加策略分析
测试代码
#include "apue.h"
#define BSZ 48
int main()
{
FILE *fp1, *fp2, *fp3;
char buf1[BSZ], buf2[BSZ], buf3[BSZ];
//方案一
memset(buf1, 'a', BSZ-2);
buf1[BSZ-2] = '\0';
buf1[BSZ-1] = 'X';
if ((fp1 = fmemopen(buf1, BSZ, "w+")) == NULL)
err_sys("fmemopen failed");
fprintf(fp1, "hello, world");
//调用fflush函数引起缓冲区冲洗
fflush(fp1);
printf("1.After fflush: %s\n", buf1);
printf("1.Len of string in buf = %ld\n", (long)strlen(buf1));
memset(buf1, 'b', BSZ-2);
buf1[BSZ-2] = '\0';
buf1[BSZ-1] = 'X';
//二次输入数据为"nihao",长度较短
fprintf(fp1, "nihao");
fclose(fp1);
printf("1.After close: %s\n", buf1);
printf("1.Len of string in buf = %ld\n\n", (long)strlen(buf1));
//方案二
memset(buf2, 'a', BSZ-2);
buf2[BSZ-2] = '\0';
buf2[BSZ-1] = 'X';
if ((fp2 = fmemopen(buf2, BSZ, "w+")) == NULL)
err_sys("fmemopen failed");
fprintf(fp2, "hello, world");
//调用fseek函数引起缓冲区冲洗,偏移值设为首部
fseek(fp2, 0, SEEK_SET);
printf("2.After fseek: %s\n", buf2);
printf("2.Len of string in buf = %ld\n", (long)strlen(buf2));
memset(buf2, 'b', BSZ-2);
buf2[BSZ-2] = '\0';
buf2[BSZ-1] = 'X';
//二次输入数据为"nihao",长度较短
fprintf(fp2, "nihao");
fclose(fp2);
printf("2.After close: %s\n", buf2);
printf("2.Len of string in buf = %ld\n\n", (long)strlen(buf2));
//方案三
memset(buf3, 'a', BSZ-2);
buf3[BSZ-2] = '\0';
buf3[BSZ-1] = 'X';
if ((fp3 = fmemopen(buf3, BSZ, "w+")) == NULL)
err_sys("fmemopen failed");
fprintf(fp3, "hello, world");
//调用fseek函数引起缓冲区冲洗,偏移值设为首部
fseek(fp3, 0, SEEK_SET);
printf("3.After fseek: %s\n", buf3);
printf("3.Len of string in buf = %ld\n", (long)strlen(buf3));
memset(buf2, 'b', BSZ-2);
buf2[BSZ-2] = '\0';
buf2[BSZ-1] = 'X';
//二次输入数据为"hello, world! How are you?",长度较长
fprintf(fp3, "hello, world! How are you?");
fclose(fp3);
printf("3.After close: %s\n", buf3);
printf("3.Len of string in buf = %ld\n\n", (long)strlen(buf3));
return(0);
}
最后的输出结果为:
1.After fflush: hello, world
1.Len of string in buf = 12
1.After close: bbbbbbbbbbbbnihao
1.Len of string in buf = 17
2.After fseek: hello, world
2.Len of string in buf = 12
2.After close: nihaobbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
2.Len of string in buf = 46
3.After fseek: hello, world
3.Len of string in buf = 12
3.After close: hello, world! How are you?
3.Len of string in buf = 26
结果分析
- 方案一:
fclose函数冲洗内存流导致需要增加流缓冲区中数据量,在当前偏移值后面继续写入数据,由12增加到17,并且调用了fclose函数,所以追加null;- 方案二:
fclose函数冲洗内存流时,偏移值在流缓冲区首部,此时"nihao"的长度小于原本流缓冲区中"hello, world"的长度,只会覆盖流缓冲区的前一部分,流缓冲区数据量并没有增加,仍未12,所以不追加null;- 方案三:同方案二,只是再次输入的数据"hello, world! How are you?"大于原本流缓冲区中的"hello, world"的长度,所以需要增加流缓冲区中数据量,调用了
fclose函数,所以追加’null`;
总结
由上述测试可以发现,追加null的策略机制是必须同时满足以下两个条件:
- 需要增加流缓冲区中的数据量
- 调用fclose、fflush、fseek、fseeko以及fsetpos时
书上的图5-15就是因为输入数据太短没有增加流缓冲区所以才没有追加null的,看到这里应该可以明白了吧。这一部分书上也没有讲得很清楚,我看到这里困惑了好久,终于通过自己的测试一点一点搞明白了。
文章粗浅,有什么疏漏之处欢迎各位批评指正!
Github: https://github.com/haoyuanliu
个人博客: http://haoyuanliu.github.io/
个人站点,欢迎访问,欢迎评论!
UNIX编程之冲洗内存流与null追加策略(APUE F5-15)的更多相关文章
- 内存流和null字节
#include <stdio.h> #include <string.h> int main() { ]={}; FILE* fp = fmemopen(buf,," ...
- UNIX高级环境编程(7)标准IO函数库 - 二进制文件IO,流定位,创建临时文件和内存流
1 二进制IO(Binary IO) 在前一篇我们了解了逐字符读写和逐行读写函数. 如果我们在读写二进制文件,希望以此读写整个文件内容,这两个函数虽然可以实现,但是明显会很麻烦且多次循环明显效率很低. ...
- linux编程 fmemopen函数打开一个内存流 使用FILE指针进行读写访问
fmemopen()函数打开一个内存流,使你可以读取或写入由buf指定的缓冲区.其返回FILE*fp就是打开的内存流,虽然仍使用FILE指针进行访问,但其实并没有底层文件(并没有磁盘上的实际文件,因为 ...
- UNIX编程艺术
本文主要是 <UNIX编程艺术>的摘录,摘录的主要是我觉得对从事软件开发有用的一些原则. 对于程序员和开发人员来说,如果完成某项任务所需要付出的努力对他们是个挑战却又恰好还在力所能及的范围 ...
- Mybatis拦截器 mysql load data local 内存流处理
Mybatis 拦截器不做解释了,用过的基本都知道,这里用load data local主要是应对大批量数据的处理,提高性能,也支持事务回滚,且不影响其他的DML操作,当然这个操作不要涉及到当前所lo ...
- [源码]ObjectIOStream 对象流 ByteArrayIOStream 数组流 内存流 ZipOutputStream 压缩流
1.对象流 import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File ...
- 在 JNI 编程中避免内存泄漏
JAVA 中的内存泄漏 JAVA 编程中的内存泄漏,从泄漏的内存位置角度可以分为两种:JVM 中 Java Heap 的内存泄漏:JVM 内存中 native memory 的内存泄漏. Java H ...
- C#流总结(文件流、内存流、网络流、BufferedStream、StreamReader/StreamWriter、TextReader/TextWriter)
一.文件流 FileStream类主要用于读写磁盘文件.常用于向磁盘存储数据或读取配置文件. 读取文件: //文件流:读取 FileStream fileStream = File.Open(@&qu ...
- 如何在MVC中显示条形码图片(以内存流的方式)
前台代码: <script type="text/javascript"> function fresh() { var getimagecode = document ...
随机推荐
- WordPress4.1新的函数介绍
wordpress 4.1也更新了一阵子了,作为一般用户最关系的就是新的wordpress主题,作为开发者,新增的函数也给我们带来了更多的便捷和惊喜,今天就转载一篇介绍wordpress4.1中新增的 ...
- OC文件操作(1)
1.文件的浅度遍历与深度遍历: //NSFileManager * fm = [[NSFileManager alloc]init];//创建文件管理器 //第一步创建一个文件管理器 NSError ...
- Java常量和变量
1.Java运行原理 编译+解释型语言: 程序代码经编译后转换为一种称为java字节码(.class文件)的中间语言 file.java--->Class.class java虚拟机JVM将字节 ...
- js 代码记录
window.screen.availWidth 返回当前屏幕宽度(空白空间) window.screen.availHeight 返回当前屏幕高度(空白空间) window.screen.width ...
- MySql数据库2【常用命令行】
(一) 连接MYSQL: 格式: mysql -h主机地址 -u用户名 -p用户密码 1.连接到本机上的MYSQL 进入mysql安装目录下的bin目录下,再键入命令mysql -uroot -p,回 ...
- 用Raphael在网页中画圆环进度条
原文 :http://boytnt.blog.51cto.com/966121/1074215 条状的进度条我们见得太多了,实现起来比较简单,它总是长方形的,在方形的区域里摆 放就不太好看了.随着cs ...
- orcad中的PSpice仿真加入厂商模型
<1>首先要知道原理图的符号是没有模型的,不是你肆意妄为就可以拉来仿真的. <2>其次要知道很多器件软件中是没有模型的. <3>有很多获取模型的方法:<使 ...
- Phalcon的学习篇-phalcon和devtools的安装和设置
A Phalcon在Windows上的安装 1 从Phalcon for Windows下载适合的DLL, 这里的适合 主要看两个方面 1 PHP的版本 2 线程是否是安全 3 编译版本 如果不清楚这 ...
- 【Java】ArrayList和LinkedList的区别
一般大家都知道ArrayList和LinkedList的大致区别: 1.ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构. 2.对于随机访问 ...
- Hibernate中的事务隔离
在我们的项目中,老发现程序报告sesssion is closed或者因数据已经被其他事务修改而导致当前事务无法提交,由于系统的运行用户最多也就几十个人,所以考虑使用严格的事务隔离来防止这种类型的问题 ...