2014-09-11 22:14:03 SahPah 阅读数 1447

    今天在看APUE3进程章节的fork函数的时候,有一个例程中使用了格式化IO函数(printf),在输出到终端的时候只输出一次,但重定向到某个文件时,却由于子进程及缓冲更改的关系输出了两次。具体代码如下:

#include "apue.h"

int globvar = 6; /* external variable in initialized data */
char buf[] = "a write to stdout\n";

int  main(void) 
{
        int var; /* automatic variable on the stack */
        pid_t pid;

        var = 88;
        if (write(STDOUT_FILENO, buf, sizeof(buf)-1) != sizeof(buf)-1)
                err_sys("write error");
        printf("before fork\n"); /* we don't flush stdout */

        if ((pid = fork()) < 0) {
                err_sys("fork error");
        } else if (pid == 0) { /* child */
                globvar++; /* modify variables */
                var++;
        } else {
                sleep(2); /* parent */
        }

        printf("pid = %ld, glob = %d, var = %d\n", (long)getpid(), globvar, var); 
        exit(0);
} 

输出如下:

sahpah:~/apue.3e/proc$ ./fork1 
a write to stdout 
before fork 
pid = 14761, glob = 7, var = 89 
pid = 14760, glob = 6, var = 88 
sahpah:~/apue.3e/proc$ ./fork1 > h 
sahpah:~/apue.3e/proc$ cat h 
a write to stdout 
before fork 
pid = 15649, glob = 7, var = 89 
before fork 
pid = 15648, glob = 6, var = 88 


这是由于在Linux下,默认符合如下缓冲特征:
  • 当且仅当标准输入和标准输出并不指向交互式设备时,它们才是全缓冲的。
  • 标准错误决不会是全缓冲的。

因此,在标准输出直接输出到终端时,它是行缓冲的,由换行符冲洗。
所以,在fork之前第一个个printf函数由换行符将缓冲区冲洗完,仅打印一次“before fork”。
 printf("before fork\n"); 

    当使用“./fork1 > h”重定向到h文件时,使用的是全缓冲,fork前的printf缓冲区中的数据并不会被冲洗,而fork出来的子进程获得了父进程数据空间、堆和栈的副本,所以子进程也继承了父进程缓冲区中的数据,即父进程和子进程都有了该行的缓冲区,随后第二个printf将数据追加到该缓冲区中,exit函数在退出前将各自的缓冲区进行冲洗,于是打印了两次“before fork”。

修改方法:
1.在第一个printf后使用fflush函数强制对标准输出流进行冲洗:
#include "apue.h"

int globvar = 6; /* external variable in initialized data */
char buf[] = "a write to stdout\n";

int  main(void) 
{
        int var; /* automatic variable on the stack */
        pid_t pid;

        var = 88;
        if (write(STDOUT_FILENO, buf, sizeof(buf)-1) != sizeof(buf)-1)
                err_sys("write error");
        printf("before fork\n"); /* we don't flush stdout */
        fflush(stdout); //force flush

        if ((pid = fork()) < 0) {
                err_sys("fork error");
        } else if (pid == 0) { /* child */
                globvar++; /* modify variables */
                var++;
        } else {
                sleep(2); /* parent */
        }

        printf("pid = %ld, glob = %d, var = %d\n", (long)getpid(), globvar, var); 
        exit(0);
} 

进行强制冲洗后输出如下:
sahpah:~/apue.3e/proc$ ./fork1 
a write to stdout 
before fork 
pid = 14761, glob = 7, var = 89 
pid = 14760, glob = 6, var = 88 
sahpah:~/apue.3e/proc$ ./fork1 > h 
sahpah:~/apue.3e/proc$ cat h 
a write to stdout 
before fork 
pid = 15649, glob = 7, var = 89 
pid = 15648, glob = 6, var = 88 

2.使用setvbuf设置标准输出流的缓冲类型:

#include "apue.h"

int globvar = 6; /* external variable in initialized data */
char buf[] = "a write to stdout\n";

int  main(void) 
{
        int var; /* automatic variable on the stack */
        pid_t pid;

        var = 88;

        setvbuf(stdout, NULL, _IOLBF, 0);//set buffer

        if (write(STDOUT_FILENO, buf, sizeof(buf)-1) != sizeof(buf)-1)
                err_sys("write error");
        printf("before fork\n"); /* we don't flush stdout */

        if ((pid = fork()) < 0) {
                err_sys("fork error");
        } else if (pid == 0) { /* child */
                globvar++; /* modify variables */
                var++;
        } else {
                sleep(2); /* parent */
        }

        printf("pid = %ld, glob = %d, var = %d\n", (long)getpid(), globvar, var); 
        exit(0);
} 

    setvbuf将stdout流类型重设置为行缓冲,但其缓冲区由我们指定,此处指向NULL,则标准IO库自动为该流分配适当长度的缓冲区。当数据超过缓冲区长度时,缓冲区则被冲洗,而此处缓冲区长度设置为0,故每个接收的数据都立即被冲洗。

输出如下:
sahpah:~/apue.3e/proc$ ./fork1 
a write to stdout 
before fork 
pid = 43680, glob = 7, var = 89 
pid = 43679, glob = 6, var = 88 
sahpah:~/apue.3e/proc$ ./fork1 > h 
sahpah:~/apue.3e/proc$ cat h 
a write to stdout 
before fork 
pid = 43682, glob = 7, var = 89 
pid = 43681, glob = 6, var = 88 


2019-04-17 16:43:43 qq_41453285 阅读数 47

待续

2019-05-24 19:33:31 qq_28090573 阅读数 31

上一篇

五、每次一行的IO

        上一篇总结到了标准IO库中,单个字符的IO操作。紧接上篇,Unix中提供了两个 以行为单位操作的IO:

#include<stdio.h>

char *fgets(char *restrict buf, int n,FILE* restrict  fp);

char *gets(char *buf);

                               返回值:若成功,返回buf;若出错或到文件尾端,返回EOF

 gets 是从标准输入读,fgets 是从指定的文件流中读。

        书中对这两个函数进行了介绍,注意:不建议使用gets这个函数!!!新的 ISO C 标准中已经不用gets这个接口了

        在上述中,有一句话“读入的字符送入缓冲区,该缓冲区以null字符结尾。”何为null字节????

                          

其实就是我们常用的,字符串的结尾  '\0'

 

#include<stdio.h>

char *fputs(const char *restrict buf, int n,FILE* restrict  fp);

char *puts(char *buf);

                               返回值:若成功,返回非负值;若出错,返回EOF

这两个函数与上述两个读函数对应,puts 是写到标准输出,fputs 写入指定文件流。puts不像gets那么不安全,但也不建议使用。

        函数 fputs,是将一个以 '\0' 结尾的字符串写入到指定流中,并不是一次输出一行遇到 '\0' 就输出。

        函数 puts,是将一个以 '\0' 结尾的字符串写入到标准输出中。

 

六、二进制的IO

        由于fgetc 和 fputc 一次只能读取一个字符,fgets 和 fputs 遇到 '\0'就结束一次操作。对于读写二进制数组,或是结构都不是很好用。故引入fread 和 fwrite ,个人也是觉得这两个函数用的最多。

#include<stdio.h>

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

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

                               返回值:返回读写的对象个数

 书中介绍了以下两种运用场景:

        fread 在读对象时,所返回的个数要是小于想要读取的 objNum ,则有可能是两种错误,要么出错要么到文件尾。故需要ferror 或 feof 去判断是何种错误。

        fwrite 在写对象时,所返回的个数要是小于想要写入的 objNum,必是出错。

        对于fread 和 fwrite而言,有一个致命的问题。那就是它只能用于在同一个操作系统上使用。在早期网络并不发达的环境中,这不算问题,但是现在许多异构系统通过网络连接起来,这个系统写数据,那个系统处理数据变的常见。故这两个函数不能使用。书中解释原因如下:

 

七、定位流

        在定位流这里,书里介绍了三组函数,我只用过第一组。全部整理一下,也长长见识。

#include<stdio.h>

long ftell(FILE*  fp);

                           返回值:若成功,返回当前文件位置指示;若出错,返回-1L

int fseek(FILE* fp, long offset, int whence);

                           返回值:若成功,返回0;若出错,返回-1

void rewind(FILE* fp);

        以上这三个函数都是以字节为度量的

        函数 ftell ,返回的是在当前的文件流的字节位置。

        函数 fseek, 是将该流偏移offset个字节,从whence位置

                SEEK_SET : 表示文件的开头;SEEK_CUR :表示文件的当前位置;SEEK_END:表示文件的结尾

        函数 rewind,是将文件流位置放到文件的起始位置

        其他两组直接截书中原图:

 

八、格式化IO

1、格式化的输出

        总共有五个函数,如下:

        通过函数的定义可以比较清楚的明白其作用,printf 是输出到 stdout ,其他的目标有的是流、有的是文件描述符。需要注意的是, 我个人比较喜欢用 sprintf ,将复杂的数据格式化成字符串时比较好用,现在应该尽量使用 snprintf ,要告诫自己注意缓冲区的大小,避免越界!!! 

      其格式化的方式,格式如下(有些没咋用用过,方便查找):

       [flags] : 

        [fldwidth]:

         [precision] : 

        [lenmodifier] :说明参数的长度

 

 

还有一套printf 族函数的变体函数,将可变参数表(...),替换成了 va_list arg

 

2、格式化输入

        格式化输入总共有三个函数,是将流中的输入字符串,按照 format 格式,输出到参数列表中。

   

        以上部分都是书中原文

       

下一篇

2010-08-22 01:07:46 Diablogs 阅读数 26
#include <stdio.h>
#include <stdarg.h>

格式化输入:
int scanf(const char *fmt, ...);
int sscanf(const char *buf, const char *fmt, ...);
int fscanf(FILE *fp, const char *fmt, ...);
int vscanf(const char *fmt, va_list arg);
int vsscanf(const char *buf, const char *fmt, va_list arg);
int vfscanf(FILE *fp, const char *fmt, va_list arg);

格式化输出:
int printf(const char *fmt, ...);
int sprintf(char *buf, const char *fmt, ...);
int snprintf(char *buf, size_t n, const char *fmt, ...);
int fprintf(FILE *fp, const char *fmt, ...);
int vprintf(const char *fmt, va_list arg);
int vsprintf(char *buf, const char *fmt, va_list arg);
int vsnprintf(char *buf, size_t n, const char *fmt, va_list arg);
F0int vfprintf(FILE *fp, const char *fmt, va_list arg);

其中带 v 开头的函数是将可变参数表(...)换成 va_list 类型,其它函数就不说了。
本来是想学习一下使用可变参数函数的,可变参数表用到2个函数:
va_start
va_end

//test.c

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <time.h>

#define MAXLINE 4096

void w_log(char *str, ...)

FILE *fp;
char szfilename[MAXLINE];
char szstr[MAXLINE];
time_t now;
struct tm t;
va_start(vargs, str);
vsnprintf(szstr, MAXLINE, str, vargs);
va_end(vargs);

time(&now);
localtime_r(&now, &t);
sprintf(szfilename, "./w_log.%4d%02d%02d", t.tm_year+1900, t.tm_mon+1, t.tm_mday);
fp = fopen(szfilename, "a+");
if (fp == NULL)
return;
fprintf(fp, "[%4d-%02d-%02d %02d:%02d:%02d] %s\n", t.tm_year+1900, t.tm_mon+1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, szstr);
fclose(fp);
}
void main()
{
int i;
char sztest[]="gogo args";
i = 1111;
w_log("testtest......");
w_log("%d_%s", 10, sztest);
w_log("%d,%s,%d,%s", 10, sztest, i, "hahaha...");
return;
}
--------------------------------------------------------------------------
结果:$$$$$$$$$$$$$$$$$$$$$$$$$$$$$

[sand@localhost testfmt]$ gcc -o test test.c
[sand@localhost testfmt]$ ./test
[sand@localhost testfmt]$ cat w_log.20090702
[2009-07-02 16:24:04] testtest......
[2009-07-02 16:24:04] 10_gogo args
[2009-07-02 16:24:04] 10,gogo args,1111,hahaha...
[sand@localhost testfmt]$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
2015-06-19 17:15:46 iEearth 阅读数 1165

Linux系统的文件IO都是针对文件描述符的,而标准IO(ISO C)的操作则是围绕流进行的,一个最明显的区别是标准IO比Linux文件IO多了缓冲机制。为了使用流,需要用到文件指针即指向FILE结构的指针,在“libio.h”头文件中有FILE结构的详细说明。“stdio.h”头文件中定义了三个标准流,stdin、stdout和stderr,分别是标准输入、标准输出和标准出错。流可以分为非格式化IO和格式化IO,前者又分为字符IO、行IO和二进制IO,后者如常见的scanf、printf函数等。对于ASCII字符集,有的字符用一个字节(单字节)表示,有的字符用多个字节(宽字节)表示,当一个流最初被创建时,它并没有定向,若在未定向的流上使用一个多字节IO函数,则将该流的定向设置为宽定向,若在未定向的流上使用一个单字节IO函数,则将该流的定向设置为字节定向,更改流定向只有两个函数,freopen和fwide,freopen用以清除流定向,fwide用于设置流定向。

#include <stdio.h>
FILE *freopen(const char *path, const char *mode, FILE *stream);
#include <wchar.h>
int fwide(FILE *stream, int mode);

缓冲——

标准IO提供缓冲的目的是尽可能地减少read和write系统调用的次数,它对每个IO流自动地进行缓冲管理,从而避免了应用程序需要考虑这一点所带来的麻烦。标准IO提供了三种类型的缓冲,全缓冲、行缓冲和无缓冲。全缓冲在填满标准IO缓冲区后才进行实际IO操作;行缓冲在输入和输出中遇到换行符或行缓冲区填满时执行IO操作;无缓冲即标准IO不对字符进行缓冲存储。一般清空下,有默认的缓冲方式,标准出错流stderr不带缓冲,从而使得出错信息可以尽快显示出来,涉及终端设备时流是行缓冲的,其它情况是全缓冲的,更改缓冲方式可通过setbuf和setvbuf函数完成,setvbuf可以指定具体的缓冲方式(mode),在“stdio.h”头文件中有一个适合系统的缓冲区长度宏定义BUFSIZ。

#include <stdio.h>
void setbuf(FILE *stream, char *buf);
int setvbuf(FILE *stream, char *buf, int mode, size_t size);

调用fflush函数可强制冲洗一个流或所有输出流:

#include <stdio.h>
int fflush(FILE *stream);

当一个进程正常终止时,即调用exit函数或者从main函数返回,那么所有未写的带缓冲数据的标准IO流都会被冲洗,所有打开的标准IO流都会被关闭。

打开与关闭流——

#include <stdio.h>
FILE* fopen(const char *path, const char *mode);
FILE* fdopen(int fd, const char *mode);
FILE* freopen(const char *path, const char *mode, FILE *stream);
int fclose(FILE *fp);

上面前三个函数可以打开一个标准IO流,fopen打开指定的文件,freopen在预定义的流(一般为stdin/stdout/stderr)上打开指定的文件,fdopen使得流关联到一个文件描述符上。fclose函数用以关闭一个打开的流。

流状态——

下面三个函数用于检查、设置流的状态,如EOF标志(一般为-1)、错误标志。

#include <stdio.h>
int ferror(FILE *stream);
int feof(FILE *stream);
void clearerr(FILE *stream);

流定位——

下面列出的是用于流定位的几个函数,它们基于不同的标准。

#include <stdio.h>
int ftell(FILE *stream);
int fseek(FILE *stream, long offset, int whence);
void rewind(FILE *stream);
int fgetpos(FILE *stream, fpos_t *pos);
int fsetpos(FILE *stream, fpos_t *pos);
off_t ftello(FILE *stream);
int fseeko(FILE *stream, off_t offset, int whence);

字符IO——

#include <stdio.h>
int getc(FILE *stream);
int fgetc(FILE *stream);
int getchar(void);
int ungetc(int c, FILE *stream);
int putc(int c, FILE *stream);
int fputc(int c, FILE *stream);
int putchar(int c);

在上面的字符IO中,前三个get系列函数用于一次读取一个字符,其中getchar等效于getc(stdin),getc可实现为宏而fgetc不能,这意味着getc的参数不能是有副作用的表达式,fgetc的函数地址看作为参数传递,fgetc耗时可能比getc要长。ungetc函数用于把字符压送会流中,后面三个put系列函数则用于一次输入一个字符。

行IO——

#include <stdio.h>
char* gets(char *s);
char* fgets(char *s, int size, FILE *stream);
int puts(const char *s);
int fputs(const char *s, FILE *stream);

在上面的行IO中,get系列函数用于一次读取一行,put系列函数用于一次输入一行,需要注意的是gets由于未指定行的长度,有可能造成缓冲区溢出,这几个函数的行尾换行符也要注意。

二进制IO——

#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

上面的两个二进制IO在处理复杂的数据结构时,如果在不同的机器上执行,可能会有问题,如编译方式、字节对齐等问题。

格式IO——

下面列出的是常见的格式化输入、输出IO。

#include <stdio.h>
int printf(const char *format, …);
int fprintf(FILE *stream, const char *format, ...);
int sprintf(char *str, const char *format, ...);
int snprintf(char *str, size_t size, const char *format, ...);
int scanf(const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
int sscanf(const char *str, const char *format, ...);
#include <stdarg.h>
int vprintf(const char *format, va_list ap);
int vfprintf(FILE *stream, const char *format, va_list ap);
int vsprintf(char *str, const char *format, va_list ap);
int vsnprintf(char *str, size_t size, const char *format, va_list ap);
int vscanf(const char *format, va_list ap);
int vsscanf(const char *str, const char *format, va_list ap);
int vfscanf(FILE *stream, const char *format, va_list ap);

printf系列函数的format中,需要进行格式控制时由百分号开始,其余字符则原样输出。格式控制形式如下:

%[flag][width][precision][modifier]type

flag可选,“-”表示左对齐,默认右对齐,“+”表示输出正负号,默认正数和零不输出,“ ”即空格,表示不带正负号时在前面添加空格,默认不添加,“#”表示添加进制符号,如十六进制的0x,默认不添加,“0”表示填充数字零,默认使用空格填充。

width表示输出的字符宽度,是一个十进制数,或是一个星号“*”,星号的意思是具体宽度由后面的参数给出。

precision表示精度,对于整型来说表示最少输出位数,对于浮点型来说表示小数点后的最少位数,对于字符串来说表示最多输出位数,precision以小数点开始,后接一个十进制整数或者星号“*”。

modifier表示参数长度,如下:
这里写图片描述

type表示参数类型,如下:
这里写图片描述

临时文件——

标准IO库还提供了用于创建临时文件的若干函数,tmpnam、tempnam、mktemp用以构建临时文件名,tmpfile、mkstemp用于创建临时文件。

#include <stdio.h>
FILE *tmpfile(void);
char *tmpnam(char *s);
char *tempnam(const char *dir, const char *pfx);
#include <stdlib.h>
int mktemp(char *template);
int mkstemp(char *template);

标准IO不足——

标准IO库的一个不足之处是效率不高,这与它需要复制的数据量有关。当使用每次一行函数fgets和fputs时,通常需要复制两次数据:一次是在内核和标准IO缓冲之间(当调用read和write时),第二次是在标准IO缓冲区和用户程序中的行缓冲区之间。而快速IO库则避免了这一点,其方法是使读一行的函数返回指向该行的指针,而不是将该行复制到另一个缓冲区。

没有更多推荐了,返回首页