1 二进制IO(Binary IO)

在前一篇我们了解了逐字符读写和逐行读写函数。

如果我们在读写二进制文件,希望以此读写整个文件内容,这两个函数虽然可以实现,但是明显会很麻烦且多次循环明显效率很低。

为了应对这种场景,标准IO库提供了fread和fwrite函数。

函数声明:

#include <stdio.h>

size_t fread(void *restrict ptr, size_t size, size_t nobj, FILE *restrict fp);

size_t fwrite(const void *restrict ptr, size_t size size_t nobj, FILE *restrict fp);

函数用法;

a) 读写一个数组。

float data[10];

if (write(&data[2], sizeof(float), 4, fp) != 4)

    err_sys(“fwrite error");

本例中,从流fp中读取4个float型数据填入到数组下表从2到5得位置中。

b) 读写一个结构体

struct {

    short  count;

    long   total;

    char   name[NAMESIZE];

} item;

if (fwrite(&item, sizeof(item), 1, fp) != 1)

    err_sys(“fwrite error");

本例中,从fp读取数据填入到一个结构体中。

 

上面两例都可以认为是读写一个结构体的数组,参数size是结构体的长度,参数nobj是数组中要读写的元素的个数。

 

函数返回值:

两个函数的返回值都是读写的元素个数。

对于读函数,返回值可能会比nobj小,如果有异常抛出或者读到了文件结尾。这时需要调用函数ferror或feof来判断。

对于写函数,返回值比nobj小,则一定是有异常抛出。

 

函数细节:

在上面的例子中,我们通过fwrite函数填充了一个结构体,那么如果读写不在一个系统中,那么结构体的内存布局可能并不相同,这对于现在的多系统互联工作的场景下很常见。我们会在讨论socket时回来继续看这个问题,实际的解决方案就是在不同系统间读写二进制数据时使用相同的协议。

 

2 定位流(Positioning a Stream)

我们有三种方法对流进行定位:

  • 函数ftell和fseek。将文件的当前偏移位置存储在long integer型变量中;
  • 函数ftello和fseeko。将文件的当前偏移量存储在off_t型变量中;
  • 函数fgetpos和fsetpos。使用数据类型fpos_t记录文件的当前偏移量。

 

ftell和fseek函数声明:

#include <stdio.h>

long ftell(FILE* fp);    // Returns:current file position indicator if OK, -1L on error

int fseek(FILE* fp, long offset, int whence);       // Returns:0 if OK , -1 on error

void rewind(FILE* fp);

函数细节:

  • 二进制文件的偏移量是从文件开始到当前位置的字节数;
  • ftell函数返回当前文件的偏移位置;
  • fseek函数用来定位文件到指定偏移位置;
  • fseek函数的参数whence,用来设置计算偏移量的方法:SEEK_SET表示从文件开头开始计算,SEEK_CUR表示从文件当前偏移位置开始计算,SEEK_END表示从文件结尾开始计算。
  • 对于一些非Unix操作系统,存储文本文件的存储格式会有所不同,当前文件偏移量无法通过字节数来表示,这种情况下,参数whence需要设置为SEEK_SET,并且offset只有两个值可以使用:0,表示倒回文本开头;另一个可用值为函数ftell的返回值。

 

ftello和fseeko函数声明:

#include <stdio.h>

off_t ftello(FILE* fp);     // Returns: current file position indicator if OK, (off_t) -1 on error

int fseeko(FILE* fp, off_t offset, int whence);     /// Returns: 0 if OK, -1 on error

函数细节:

  • 这两个函数和上面的ftell和fseek功能相同,只是返回值类型不是long,而改成了off_t,实现上可以让off_t的表示范围更大。

 

fgetpos和fsetpos函数声明:

#include <stdio.h>

int fgetpos(FILE* restrict fp, fpos_t *restrict pos);

int fsetpos(FILE* fp, const fpos_t pos);

函数细节:

  • fgetpos函数保存当前文件偏移量到参数pos中
  • fgetpos得到的pos可以用来使用fsetpos设置当前文件偏移量到之前的位置。

 

3 格式化输入输出

格式化输出函数

有五个printf函数负责格式化输出。

函数声明:

#include <stdio.h>

int printf(const char *restrict format, ...);

int fprintf(FILE *restrict fp, const char *restrict format, ...);

int dprintf(int fd, const char *restrict format, ..);

      // All three return : number of characters output if OK , negative value if output error

int sprintf(char *resrict buf, const char *restrict format, ...);

      // Returns: number of characters stored in array if OK, negative value if encoding error

int snprintf(char *restrict buf, size_t n, const char *restrict format, ...);

      // Returns: number of characters,that would have been stored in array if buffer was large enough, negative value if encoding error

函数细节:

  • printf输出到标准输出;
  • fprintf输出到指定的流中;
  • dprintf输出到指定的文件描述符中;
  • sprintf将格式化字符串写入到指定的buffer数组中,自动在结尾处加上一个null结尾符,但是不计入返回值中,并且,sprintf在buffer不够大时可能发生越界,因此需要使用者保证buffer足够大;
  • snprintf防止越界,在springf的参数中增加了buffer的大小参数,所有越界写入的字符都被忽略,如果返回值比buffer得长度要小,则说明输出没有被截断。

 

格式化输入函数

函数声明:

#include <stdio.h>

int scanf(const char *restrict format, ...);

int fscanf(FILE *restrict fp, const char *restrict format, ...);

int sscanf(const char *restrict buf, const char *restrict format, ...);

函数细节:

  • format参数后面接得参数,包含存放读入字符串的变量地址。

更多关于格式化输入输出的细节可以自己查询Unix操作系统手册。

 

4 从流中获取文件描述符

函数声明:

#include <stdio.h>

int fileno(FILE* fp);       // Returns: the file descriptor associated with the stream

如果我们需要调用dup和fcntl,则需要调用该函数。

 

5 临时文件(Temporary Files)

标准IO库提供了两个函数用于创建临时文件。

函数声明:

#include <stdio.h>

char* tempnam(char *ptr);

FILE* tmpfile(void);

函数细节:

  • 函数tmpnam生成一个字符串,该字符串为一个合法的路径名,并且不和任何已存在的文件重复。
  • 函数tmpnam每次调用都生成不同的字符串,知道TMP_MAX次数。
  • 如果函数tempnam的参数ptr为NULL,则生成的路径字符串存在内存静态区,函数返回值为指向该路径字符串的指针。如果随后再次使用null参数调用tempnam,会覆盖之前生成的字符串。
  • 如果函数tempnam的参数ptr不是NULL,那么生成的路径字符串存在ptr指向的数组内,所以需要保证ptr指向的数组的长度至少为L_tmpnam。
  • 函数tmpfile函数创建一个临时二进制文件(type wb+),程序终止或者该文件被关闭,则该文件自动被删除。对于UNIX操作系统而言,生成一个二进制文件并没有什么影响,因为内核并不区分文本文件还是二进制文件。

Example:

Code:

#include "apue.h"

 

int

main(void)

{

    char    name[L_tmpnam], line[MAXLINE];

    FILE    *fp;

 

    printf("%s\n", tmpnam(NULL));       /* first temp name */

 

    tmpnam(name);                       /* second temp name */

    printf("%s\n", name);

 

    if ((fp = tmpfile()) == NULL)       /* create temp file */

        err_sys("tmpfile error");

    fputs("one line of output\n", fp);  /* write to temp file */

    rewind(fp);                         /* then read it back */

    if (fgets(line, sizeof(line), fp) == NULL)

        err_sys("fgets error");

    fputs(line, stdout);                /* print the line we wrote */

 

    exit();

}

 

在系统The Single UNIX Specification定义了另外两个函数处理临时文件:

函数声明:

char* mkdtemp(char* template);    // Returns: pointer to directory name if OK, NULL on error

int mkstemp(char* template);    // Returns: file descriptor if OK, -1 on error

函数细节:

  • mkdtemp函数创建一个名字唯一的文件夹
  • mkstemp函数创建一个名字唯一的常规文件(regular file)
  • 命名规则为 template + 六位随机字符

 

6 内存流(Memory Streams)

有的标准输入输出流并没有对应打开的硬盘文件,所有操作都是与内存中buffer进行数据交换,这些流被叫做内存流(memory streams)。

函数声明:

#include <stdio.h>

FILE* fmemopen(void *restrict buf, size_t size, const char *restrict type);

// Returns: stream pointer if OK, NULL on error

函数细节:

  • 参数buf指定使用的buffer,size为该buffer的大小,如果只指定size,而buf为null,那么fmemopen根据size的大小分配内存,由fmemopen分配的内存在流关闭时自动被释放;
  • 参数type控制该流的功能.

 

7 总结

标准IO函数库被大多数UNIX应用使用。

在使用的时候,注意哪里使用了buffer来处理,因为这是容易引起迷惑的地方。

 

 

参考资料:

《Advanced Programming in the UNIX Envinronment 3rd》

 

UNIX高级环境编程(7)标准IO函数库 - 二进制文件IO,流定位,创建临时文件和内存流的更多相关文章

  1. UNIX高级环境编程1

    UNIX高级环境编程1 故宫角楼是很多摄影爱好者常去的地方,夕阳余辉下的故宫角楼平静而安详. 首先,了解一下进程的基本概念,进程在内存中布局和内容. 此外,还需要知道运行时是如何为动态数据结构(如链表 ...

  2. UNIX高级环境编程(6)标准IO函数库 - 流的概念和操作

    标准IO函数库隐藏了buffer大小和分配的细节,使得我们可以不用关心预分配的内存大小是否正确的问题. 虽然这使得这个函数库很容易用,但是如果我们对函数的原理不熟悉的话,也容易遇到很多问题.   1 ...

  3. UNIX高级环境编程(14)文件IO - O_DIRECT和O_SYNC详解 < 海棠花溪 >

    春天来了,除了工作学习,大家也要注意锻炼身体,多出去运动运动.  上周末在元大都遗址公园海棠花溪拍的海棠花.   进入正题. O_DIRECT和O_SYNC是系统调用open的flag参数.通过指定o ...

  4. UNIX高级环境编程(13)信号 - 概念、signal函数、可重入函数

    信号就是软中断. 信号提供了异步处理事件的一种方式.例如,用户在终端按下结束进程键,使一个进程提前终止.   1 信号的概念 每一个信号都有一个名字,它们的名字都以SIG打头.例如,每当进程调用了ab ...

  5. UNIX高级环境编程(10)进程控制(Process Control)- 竞态条件,exec函数,解释器文件和system函数

    本篇主要介绍一下几个内容: 竞态条件(race condition) exec系函数 解释器文件    1 竞态条件(Race Condition) 竞态条件:当多个进程共同操作一个数据,并且结果依赖 ...

  6. Unix高级环境编程

    [07] Unix进程环境==================================1. 进程终止    atexit()函数注册终止处理程序.    exit()或return语句:    ...

  7. UNIX高级环境编程(9)进程控制(Process Control)- fork,vfork,僵尸进程,wait和waitpid

    本章包含内容有: 创建新进程 程序执行(program execution) 进程终止(process termination) 进程的各种ID   1 进程标识符(Process Identifie ...

  8. UNIX高级环境编程(15)进程和内存分配 < 故宫角楼 >

    故宫角楼是很多摄影爱好者常去的地方,夕阳余辉下的故宫角楼平静而安详.   首先,了解一下进程的基本概念,进程在内存中布局和内容. 此外,还需要知道运行时是如何为动态数据结构(如链表和二叉树)分配额外内 ...

  9. UNIX高级环境编程(8)进程环境(Process Environment)- 进程的启动和退出、内存布局、环境变量列表

    在学习进程控制相关知识之前,我们需要了解一个单进程的运行环境. 本章我们将了解一下的内容: 程序运行时,main函数是如何被调用的: 命令行参数是如何被传入到程序中的: 一个典型的内存布局是怎样的: ...

随机推荐

  1. synchronized修饰方法和对象的区别

    使用synchronized(object) { 代码块.... } 能对代码块进行加锁,不允许其他线程访问,其的作用原理是:在object内有一个变量,当有线程进入时,判断是否为0,如果为0,表示可 ...

  2. rpm使用方法

    查看rpm信息:rpm -q [软件的rpm名字]rpm -q下还有很多选项,具体功能如下:rpm -qa                列出所有已安装的RPM文件rpm -qa | grep [rp ...

  3. cgroups简单使用

    Cgroups控制系统资源的分配(cpu.mem.io) 1.cgroups概述 CGroup是Linux内核提供的可以限制.隔离进程组 (process groups) 所使用的物理资源 (如 cp ...

  4. centos6.5下yum安装mysql5.5

    第一步就是看linu是否安装了mysql,经过rpm -qa|grep mysql查看到centos下安装了mysql5.1,那就开始卸载咯 2 接下来就是卸载mysql5.1了,命令:rpm -e ...

  5. mvc中的action验证登录(ActionFilterAttribute)

    方法一 :  1.创建一个全局action过滤器  (在appstart  的filterconfig中注册   filters.Add(new LoginAttribute());)  2.不需要登 ...

  6. IOS中微信摇一摇声音无法播放解决办法

    在IOS中第一次调用play方法播放音频会被阻止,必须得等用户有交互动作,比如touchstart,click后才能正常调用,所以可以在摇一摇之前提醒用户点击一下开始游戏的按钮或者给用户一个弹窗,用户 ...

  7. java 获取两个日期之间的所有日期(年月日)

    前言:直接上代码 java 获取两个日期之间的所有日期(年月日) /** * 获取两个日期之间的日期,包括开始结束日期 * @param start 开始日期 * @param end 结束日期 * ...

  8. K:伸展树(splay tree)

      伸展树(Splay Tree),也叫分裂树,是一种二叉排序树,它能在O(lgN)内完成插入.查找和删除操作.在伸展树上的一般操作都基于伸展操作:假设想要对一个二叉查找树执行一系列的查找操作,为了使 ...

  9. 项目小结:手机邮箱正则,URL各种判断返回页面,input输入框输入符合却获取不到问题

    1.手机邮箱正则 近两年出来很多新号码,听说199什么的都有了- -导致以前的正则不能用了....这就很难过,总是过一段时间出一种新号码.因此,我决定使用返朴归真的手机正则. 手机正则:var reg ...

  10. HTML的代码规范

    一.语法 用两个空格来代替制表符(tab) 2.嵌套元素应当缩进一次(即两个空格). 3.对于属性的定义,确保全部使用双引号,绝不要使用单引号. 4.不要省略可选的结束标签(例如,</li> ...