参考:

https://blog.51cto.com/wodesteve/1306629

http://www.ruanyifeng.com/blog/2006/04/post_213.html 换行与回车

https://linux.die.net/man/

http://bbs.chinaunix.net/thread-889260-1-1.html signed char 与 char 类型

简述

在进行文件操作时,经常遇到一些奇怪的问题,在这里总结总结常见问题并探究其原因。

不同的读写函数

文本文件读写函数
  1. int fgetc(FILE *stream);

    从文件中以 unsigned char 读取每个字符,并将它转化为 int 型返回。当到文件末尾时或读取错误时,返回 EOF

  2. char *fgets(char *buff, int maxcount, FILE *stream);

    从文件读取字符串到 buff ,遇到以下三种情况停止:

    1. 遇到换行时,读取换行符后停止,末尾添加 \0 返回 buff。
    2. 到达文件末尾
      1. 若已读取到字符,则末尾添加 \0 返回buff。
      2. 若没有读取到字符,则返回 NULL。
    3. 读取到了maxcount-1个字符,末尾添加 \0 返回 buff。

    也就是说 fgets 会自动为读取的字符串后加 \0 ,如果有字符序列 ‘A’ ’B’ ‘\0’ ,读取出来末尾会有两个 ‘\0’。第二个参数maxcount 可以放心的设置为缓冲区的大小。

  3. int fscanf(FILE *stream, const char *format, ...);

    成功时返回匹配成功的数量,失败返回EOF。

  4. int fputc(int ch, FILE *stream);

    把int 类型转化为 unsigned char 再写入流中。

    成功时返回值是把 unsigned char 转化为 int。失败返回EOF

    注意以下的坑:

    快乐Linux —— 8.3 文件库函数IO & 常见问题
  5. int fputs(const char *str, FILE *stream);

    遇到 str 中的 \0 停止,不把写入流中。

    成功时返回非负数,失败返回EOF

  6. int fprintf(FILE *stream, const char *format, ...);

    同样遇到 \0 停止, 不把它写入流中.

    成功时返回写入流中的字节数,失败时返回负数.

二进制文件读写函数
  1. size_t fread(void *buff, size_t element_size, size_t element_num, FIEL *stream);

    参数分别是是 :获取输入的地址,一个元素的大小,元素数量,流。

    返回值是:成功读取到的元素个数。

    手册上有这么一句话:fread() does not distinguish between end-of-file and error, and callers
    must use feof(3) and ferror(3) to determine which occurred.

    意思是fread 不能区分 文件末尾 和 错误,必须和 feof 和 ferror相结合使用。

  2. size_t fwrite(void *buff, size_t element_size, size_t element_num, FIEL *stream);

    参数分别是是 :输出内容的地址,一个元素的大小,元素数量,流。

    返回值是:成功输出的元素个数。

文件操作模式

FILE *fopen(const char *path, const char *mode);

FILE *freopen(const char *path, const char *mode, FILE *stream);

成功返回文件指针,失败返回NULL.

先直接上结论:以文本文件或二进制文件不同形式打开可以混用读写函数。

区别在于当使用文本文件形式打开时。

  • 写模式 \n 系统自动将其转化为 \r\n 。
  • 文本读模式 \r\n 自动转化为 \n 。

不同类型的读写函数是可以混用的,不过要小心某些文本形式读取函数对\n读取的匹配.

快乐Linux —— 8.3 文件库函数IO & 常见问题

要小心某些文本文件读取函数的机制,例如下面。文件内容没有改变,只是读取方式改变了。

快乐Linux —— 8.3 文件库函数IO & 常见问题

结果不同是因为 sacnf 本身的工作方式就是跳过\n \t 空格等。具体可以搜索scanf 工作原理。

  1. 打开方式影响系统对 \n 的处理而对操作函数没有限制.

  2. \r(line feed) 回车 ascii 13 \n (carriage return) 换行 ascii 10

    平常我们输出 \n 时,系统自动将 \n 转化为 \r\n.

    你可以用 putchar('\n') 和 putchar('\r') 观察它们的输出 .你会发现 , 实际上stdout也是对 \n 进行处理 , 转化为 \r\n.

    更多关于换行和回车的故事: http://www.ruanyifeng.com/blog/2006/04/post_213.html

  3. 在linux平台下有fopen手册有这样一句话:This is strictly for compatibility with C89 and has no effect; the 'b' is ignored on all POSIX conforming systems, including Linux.

    意思是在打开模式中的b是为了与c89兼容,而所有兼容POSIX的系统,linux,这个b会被忽略。也就是说linux下打开文件只有一种模式,也就是二进制模式,不对文件进行任何多余的操作。

文件结束

#define EOF (-1)

首先需要澄清一点,并不是文件末尾都有 EOF 标记,以 EOF 标记判断是否到达文件末尾有时会得到错误的结果。

其次,定义变量,如果前面没有 unsigned 则一般编译器都默认为 signed 类型。后文为方便起见,也就省略了signed 详细见 http://bbs.chinaunix.net/thread-889260-1-1.html

先看一种常见的错误。

char ch;
while((ch=fgetc(fp))!=EOF)
{
    putchar(ch);
}

以上的错误,当读写文本文件时一般不会出现,但当读写二进制文件时很可能会出错。

具体来说,当读写二进制文件 fgetc 读取字符 0xff 返回 0x000000ff 被截断为 0xff 赋值给 signed char 类型,然后与EOF 判断会得到 false 结果,也就是误以为到达文件结尾。下面就这个判断过程进行解析。

( ch = fgetc(fp) ) != EOF

  1. ch = fgetc(fp)

    当读取 0xff 这个字符,fgetc 以 unsigned char 类型获取这个字符,将其转化为 int 作为返回值。unsigned char 扩展为 int型,前面加0,也就是最终返回 int 类型 0x000000ff 。然后再把其截断赋值给 char 类型,所以 ch 里面保存 0xff 。

  2. ch != EOF

    char 类型与 EOF 进行比较,因为EOF被定义为 -1 所以是 int 类型。将char 扩展为 int ,属于signed 类型扩展,前面加符号位 也就编程 0xffffffff 再与 -1 进行比较,这两个是相同的,程序错误认为到达文件末尾。

  3. 当真正到达文件末尾,fget 函数会返回 -1

关于为什么 -1 就是 signed int 类型, 这块有个 字面值常量 的知识点在这里简单说说。

其实一般的数字,默认都是signed int,例如 12,012,0x12等

unsigned 类型是末尾加u或U 例如 unsigned int 12u ,012u, 0x12u

long 类型是末尾加l或L 12L;

12ul -> usigned long 类型。

从上面的分析中我们可以预测一下可能出错的场景。

  • 我们知道一般能显示的 英文文本文件其 ascii 字符范围是 0x0~0x7f 没有所以不会出现问题。

  • 而二进制文件 字符范围是从 0~255 也就是 0x0~0xff 文件字符数一多,极有可能包含 0xff 所以有时会出错。

  • 一个中文字符是由两个字符编码的,其字符范围 0x0~0xff,所以中文文本文件有可能也出错。

解决这个问题至少有一种办法:

  1. 将char 改成 int

    这样在比较时直接是 0x000000ff 与 EOF 比较不会出现错误认为到达文件末尾。

将char 改成 unsigned char 也是错误的,而这个错误原因是不能识别文件结束,也就是说循环永真。

int feof(EILE * _stream);

feof 检测文件末尾指示器是否被设置,如果设置了,则返回非0值,否则返回0;

像下面这种程序一般都会出现多读一个 -1 的问题

快乐Linux —— 8.3 文件库函数IO & 常见问题

具体原因是,当读取文件中最后一个字符数,fgetc 读取成功所以没有设置文件末尾指示器,所以feof 返回0.继续执行读取错误设置文件末尾指示器,返回-1给ch,然后feof 返回非0值。

读取不正确

每个流都有与之相关的两个指示器: 错误指示器(error indicator), 文件末尾指示器(end-of-file indicator)。当打开流时会清除这些指示器,读取不正确时设置相应的指示器。

当读取不正确,一般有三种情况:

  1. 读取失败 设置错误指示器
  2. 匹配失败 不设置任何指示器
  3. 已到达文件末尾 设置文件末尾指示器

相应的函数

int feof(FILE *stream); 检查文件是否到达文件末尾,若到达返回非0,否则返回0

int ferror(FILE *stream); 检查是否流错误,若错误返回非0,否则返回0

clearerr(FILE *stream); 清除流中的文件末尾指示器和错误指示器

总结

在进行文件操作时,为避免一些奇怪的错误,尽量以二进制形式打开文件,使用二进制形式(fread , fwrite)进行读写。

相关文章:

  • 2022-12-23
  • 2021-12-23
  • 2021-12-04
  • 2021-07-14
猜你喜欢
  • 2021-08-31
  • 2021-10-24
  • 2022-12-23
  • 2022-12-23
  • 2021-06-15
  • 2022-12-23
相关资源
相似解决方案