fgets返回NULL,是文件结尾还是读文件错误?

fgets返回NULL,是文件结尾还是读文件错误?

用fgets读取文件返回NULL,是读文件出错还是文件结尾?
前些天遇到一个问题,使用fopen函数打开文件读数据,使用feof判断,没有到文件结尾。但是使用fgets读数据时,竟然返回空?使用perror得到了一个Success的错误码〜
然后,我码了如下测试程序,打开的文件使用了当时遇到问题的那个。

 1int main()
 3    FILE* pf = fopen("1.txt", "rb");
 4    char buf[1024] = {0};
 5    while(!feof(pf)) {
 6        if(feof(pf)) {
 7            printf("reach to end of the file\n");
 8            break;
 9        }
10        memset(buf, 0, sizeof(buf));
11        if(fgets(buf, 1024, pf) == NULL) {
12            //read file error?
13            perror("read file error!");
14            break;
15        }
16    }
17    return 0;

神奇的是:读文件失败,但错误码返回0?

1192:test weiyundao$ ./a.out
2read file error!: Undefined error: 0

然后我man了一下feof和fgets:
feof:
The function feof() tests the end-of-file indicator for the stream pointed to by stream, returning non-zero if it is set.

fgets:
Upon successful completion, fgets() and gets() return a pointer to the string. If end-of-file occurs before any characters are read, they return NULL and the buffer contents remain unchanged. If an error occurs, they return NULL and the buffer contents are indeterminate. The fgets() and gets() functions do not distinguish between end-of-file and error, and callers must use feof(3) and ferror(3) to determine which occurred.

也就是说,fgets的返回值如果为NULL,其实并不一定代表文件结尾,也可能代表出错。但是,为什么在先判断feof未到文件结尾,再判断fgets反而读到了文件结束符的标志呢?
我进行了单步调试,发现该文件的最后一行是空行,还是windows版的空行("\r\n")。这着实让我感到奇怪,以"rb"模式打开的文件,理论上讲读到'\r'就应该当成普通字符来处理。

开断点进行调试,程序在读到最后一个"\r\n"时并没有在feof处执行break,而是执行到了fgets的调用处:

 1Process 51319 stopped
 2* thread #1, queue = 'com.apple.main-thread', stop reason = step over
 3    frame #0: 0x0000000100000ec7 a.out`main at 1.c:15
 4   12          memset(buf, 0, sizeof(buf));
 5   13              if(fgets(buf, 1024, pf) == NULL) {
 6   14                  //read file error?
 7-> 15                  perror("read file error!");
 8   16                  break;
 9   17              }
10   18          }
11Target 0: (a.out) stopped.
12(lldb) p buf
13(char [1024]) $0 = ""

查看了buf中的内容,可以看到buf中的内容是空的。再单步执行后,程序输出了错误码,该错误码为0:

 1read file error!: Undefined error: 0
 2Process 51319 stopped
 3* thread #1, queue = 'com.apple.main-thread', stop reason = step over
 4    frame #0: 0x0000000100000ecc a.out`main at 1.c:16
 5   13              if(fgets(buf, 1024, pf) == NULL) {
 6   14                  //read file error?
 7   15                  perror("read file error!");
 8-> 16                  break;
 9   17              }
10   18          }
11   19          return 0;

为了证明非空行的情况下,fgets会读到包括“\r\n”的所有内容,我做了如下调试:

 1Process 51372 stopped
 2* thread #1, queue = 'com.apple.main-thread', stop reason = step over
 3    frame #0: 0x0000000100000ed1 a.out`main at 1.c:7
 4   4       {
 5   5           FILE* pf = fopen("1.txt", "rb");
 6   6           char buf[1024] = {0};
 7-> 7           while(!feof(pf)) {
 8   8               if(feof(pf)) {
 9   9                   printf("reach to end of the file\n");
10   10                  break;
11Target 0: (a.out) stopped.
12(lldb) p buf
13(char [1024]) $0 = "abc\r\n"

事实证明,fgets只要读到可见字符,便不会返回NULL。那么,如果文件中存在空行"\r\n"呢?接着修改一下源文件后,进行调试:

 1Process 51409 stopped
 2* thread #1, queue = 'com.apple.main-thread', stop reason = step over
 3    frame #0: 0x0000000100000ed1 a.out`main at 1.c:7
 4   4       {
 5   5           FILE* pf = fopen("1.txt", "rb");
 6   6           char buf[1024] = {0};
 7-> 7           while(!feof(pf)) {
 8   8               if(feof(pf)) {
 9   9                   printf("reach to end of the file\n");
10   10                  break;
11Target 0: (a.out) stopped.
12(lldb) p buf
13(char [1024]) $2 = "\r\n"

嘿嘿,神奇的事情出现了,如果读到空行,只要该空行不在文件结尾,是可以读到"\r\n"字符串的,只有读到"\r\n"并且在文件结尾时,feof判断不到文件结尾,而fgets会返回NULL以表示读到了文件结尾。注意:此时的fgets返回NULL并不代表出现读文件错误!因此,需要修改源程序如下:

 1#include <stdio.h>
 2#include <string.h>
 3#include <errno.h>
 5int main()
 7    FILE* pf = fopen("1.txt", "rb");
 8    char buf[1024] = {0};
 9    while(!feof(pf)) {
10        if(feof(pf)) {
11            printf("reach to end of the file\n");
12            break;
13        }
14        memset(buf, 0, sizeof(buf));
15        if(fgets(buf, 1024, pf) == NULL) {
16            //read file error?
17            if(errno) {
18                perror("read file error!\n");
19                break;
20            }
21            else {
22                printf("reach to end of the file\n");
23                break;
24            }
25        }