开发者社区> 问答> 正文

K&R里面第七章的一个cat程序中遇到的fclose()问题:报错

此程序是一个简单版本的cat程序,也就是主要把将几个文件的内容粘到一起输出到stdout中,若在执行程序的 时候没有指定任何文件名,则默认由stdin读取输入。源程序如下:


#include <stdio.h>

int main(int argc, char *argv[]) {
    FILE *fp;
    void filecopy(FILE *, FILE *);
    
    if (argc == 1) 
        filecopy(stdin, stdout);
    else
        while (--argc > 0) 
            if ((fp = fopen(*++argv, "r")) == NULL) {
                printf("cat: can't open file %s\n", *argv);
                return 1;
            } else {
                filecopy(fp, stdout);
                fclose(fp);
            }
    return 0;
}


/* copy the content from input file(stream) to the output file        */
void filecopy(FILE *ifp, FILE *ofp) {
    int c;

    while ((c = getc(ifp)) != EOF)
        putc(c, ofp);
}


我的试验文件是两个分别名为mes1和mes2的文件。mes1的内容为“hello”,而mes2的内容为“marry!”,并且两个文件的末端都是没有换行符的,也就是说他们都仅仅只是一行末尾不带换行符的字符串而已。当我在shell执行这个程序时,出现下面的输出:

[garvylo@utopia test]$ ./a.out mes1 mes2
hello
marry!
[garvylo@utopia test]$

也就是说,在mes1和mes2的信息被输出到stdout时都在其尾部被加上了一个换行符,这是为什么呢?


之后,我在源文件中,“ return 0;” 前面加上了" while(1); "语句,目的是想看看程序在没有退出main()之前有没有将stdout的buffer中的内容输出至stdout连接的终端(显示器)。本来,理论上,应该在加了这句“ while(1); ”之后,程序是不会把buffer中积累的数据输送到stdout终端的,因为据我所知,stdout是行缓存的(在本机试验过也确实如此),在其没有写满buffer或遇到换行符之前是不会写到stdout终端的,只有在正常结束main()函数,再由exit()函数调用fclose(),由其调用fflush()函数才会把缓存区的东西输送到stdout终端。所以,这里感觉挺疑惑的。是不是那个" fclose(fp); "的作用呢?


另外,那个“ fclose(fp); ”是不是只是解除外部文件与文件指针fp的连接,释放文件指针和此文件的缓存区呢?它对于stdout应该没有影响吧?因为,我觉得在filecopy()函数中,只是由input file 的buffer读取数据,然后存到一个中间变量c,再把c中的内容存放到stdout终端的buffer中,input file 的buffer 好像与stdout终端的buffer没有什么直接的联系。不知道是否如此?


还有最后一个问题,我在网上搜寻过了却也没有得到答案。就是那个fopen()函数, 当以“ r ”读模式打开某个文件(假设这个文件位于disk上,并且是全缓冲的)时,是不是在为这个文件开辟了缓存区后,立即由文件读取内容填满这个buffer(若文件长度小于buffer长度,则读取完文件的全部内容即可),然后对该文件的读操作其实就是对该buffer的操作。当buffer的内容读完时,再通过系统调用读取该文件的剩余内容填于buffer中,当文件指针被释放后(fclose(fp)),其缓存也会被释放。而对于”调用fflush()函数刷新缓存使buffer中的内容输送到相应的被带开为“写(r)“的文件“这一说,在以” r “打开的文件中是不存在的?一个文件可以同时被打开为”读“和”写“模式吗?如果可以,那么它们申请的应该是两个不同的缓存空间吗?


晚辈还没有学习过操作系统的知识,所以问题问得可能有点浅显,但网上搜寻不出答案,而自己也百思不解,故老烦各位前辈指教一二。

展开
收起
kun坤 2020-06-07 20:08:30 967 0
1 条回答
写回答
取消 提交回答
  • 前辈,你们在吗? @中山野鬼 @stxy0509  能帮我看看这个问题吗?

    ###### @stxy0509######

    老实说,你@我之前,我就看过你这个问题,但是我觉得挺棘手的,因为我自己觉得我也搞不定,所以没敢开腔,但是,被你@了,不站出来似乎有点乌龟了……

    1 “在mes1和mes2的信息被输出到stdout时都在其尾部被加上了一个换行符”,这个确实是不应该的,我在Windows下用vs2010试验过,并没有这样。至于linux为什么这样,我也不知道。

    2 “stdout是行缓存的(在本机试验过也确实如此)”,那你是如何证明这一点的呢?h或许你真的证明了,那我只能说linux的行为对我来说有点费解。

    你觉得fclose()另外一个文件,会强迫stdout输出吗?可是为了实际证明这一点,你把fclose()注释掉,试一下。或许我说的是错的。

    3 “input file 的buffer 好像与stdout终端的buffer没有什么直接的联系”,肯定是这样的,要联系也是程序让他们有联系,否则不乱套了?

    4 文件以"r"模式打开,而且文件是全缓冲,我的理解是缓冲区中的内容不是自动就填充的,而是你使用fread()或其他读的函数,系统会将读取的内容自动放到这个缓冲区,并返回你的读取函数。如果下次读的数据还是这个缓冲去范围内的数据,就直接从这个缓冲去复制,不直接读文件了。但是如果不在范围,还是要实际读取文件的,从而再次更新缓冲区。

    5 根据我知道的资料,fflush()是把缓冲区中的数据刷新到磁盘(真正的写入)。对于以读模式打开的文件,应该不需要同步,因为根本没有也不能修改文件;

    6 文件可以被同时打开为读和写。很多时候都是一个线程写文件,而另一个线程从这个文件读取,这算是线程通讯的一种办法。只要文件指针(读写位置)保持正确就可以了。实际上,在windows下面文件要想这样,必须打开时共享读写,不能独占式访问。但是简单的c库函数做不到这一点,能不能,听天由命了(要看读写这个文件的其他进程是否设置共享)。

    7 “如果可以,那么它们申请的应该是两个不同的缓存空间吗?",我要说的是一个FILE *指针对应一个缓冲区,如果只用一个,那不乱套,你写乱我的,我搞乱你的。多从常识,原理角度考虑问题,常识,原理应该这样,那么程序也应该这样。否则,程序也会出现问题的。

    希望对你有帮助!



    ######

    FILE结构体只维护了一个缓冲区。如果你先对一个文件调用fgets函数来到一个特定的位置,这时你想要写文件的话,你需要先fseek(fp,0,SEEK_CUR)来清空缓冲,然后才能调用写函数。

    可以去看unix环境高级编程,前几章写得非常明确。

    ######

    关于第一个问题,我用vim去创建mest1跟mest2的话就会出现跟楼主一样的换行,但如果

    printf hello > mest1
    printf marry! > mest2
    这样创建的话是不会出现换行的。

    当我用hexdump去看用vim创建的mest1的时候发现文件末尾出现了奇怪的东西,所以应该是文件编辑器问题,不关代码的事吧

    ######

    问题解决了,总结一下:

    1. 问题的根本原来出现在vim这个文本编辑器身上,就像@杨同学 所说的那样。我百度了一下,原来vim会自动在其创建的文本中的每一行末尾自动添加一个换行符(如果该行末尾本身没有换行符的话)。如果想去掉这个自动换行,可以在vim的命令mode中依次敲入“set noeol”和"set binary"(后面这个"set binary" 默认是off的,打开它好像可能会引起一些其他的问题。。这个有待研究)。敲完命令后":w"保存可以看到提示的文本字符数正好是自己输入的个数(而没有额外得被加上了1个换行符)。

    2. stdout 和 stdin 默认是行缓存的。可以通过下面的代码证明stdout是行缓存的:

    printf("hello");
    while(1);

    编译此文件,运行,可以看到并没有输出,而是一直停在了某个地方 (while(1)的作用),因为此时的数据还在stdout的buffer中,而且程序并没有退出main(),所以不能flush buffer中的内容到stdout终端。但若为printf("hello\n"),则会因stdout的buffer遭遇到了一个换行符而将buffer的内容flush到stdout终端,故在运行程序时会看到相应的“hello”输出。

    3. input file 的buffer(通过“r”模式用fopen打开文件所生成的buffer),和 output file 的buffer(通过“w”模式用fopen打开文件所生成的buffer)不是同一个东西, 他们是独立的。当调用fclose()函数时(如fflush(fp),fp为指向某个文件的文件指针,类型为FILE *),其会调用fflush函数来“刷新”fp所指向文件的缓存区。而这个“刷新”是有两层含义的。对于input file 的buffer 来说,这会丢弃缓存区中尚未被读取的内容,而对于output file 来说,这会使得buffer中的内容写入到相应的输出文件中(写入disk或相应的设备文件,通过调用相应的system call)。

    http://man7.org/linux/man-pages/man3/fflush.3.html 的description有说到这个问题。所以源码中的“fclose(fp)”并不会强迫stdout输出,他只是flush了input file 的buffer,并释放了fp这个文件指针以供下个input file使用。

    4. 驻留在disk上面的文件一般为全缓存的。当以“r”模式用fopen打开此类文件时,system会为这个文件建立一个input buffer,而此时应该还不会立马将文件的内容读至buffer,而是等到真正要由该文件读取数据时才会通过system call将文件的数据填满这个buffer,然后再由这个buffer读取数据。

    5. 文件可以同时被读和写("rw")。但这个要注意的东西很多,还没学到。。


           最后,非常感谢 @stxy0509 @优游幻世@杨同学 。能抽时间看完我这累赘的叙述并且还给出了自己的看法,让我解决了一个大疑惑。一个人的能力和经验是有限的,大家不同的看法才能引起brain storm,更快地解决问题。有你们的支持,我才能走得更远。Thanks a lot~######和库有关。查一下相关库的说明。包括stdout。c标准里,对这些具体的操作方式,没有规定的。各家按照自己喜好来。本身c语言跨平台时,要注意的,就是这些。######

    引用来自“中山野鬼”的答案

    和库有关。查一下相关库的说明。包括stdout。c标准里,对这些具体的操作方式,没有规定的。各家按照自己喜好来。本身c语言跨平台时,要注意的,就是这些。
    感谢野鬼前辈~我会注意的。
    2020-06-07 20:08:36
    赞同 展开评论 打赏
问答分类:
问答地址:
问答排行榜
最热
最新

相关电子书

更多
低代码开发师(初级)实战教程 立即下载
冬季实战营第三期:MySQL数据库进阶实战 立即下载
阿里巴巴DevOps 最佳实践手册 立即下载