研二
EOF
今天想用C写一个复制函数的程序,为马上要写的传输文件的程序做准备。但在读取文件的时候遇到了一个很诡异的问题。首先放出代码:
#include <stdio.h>
#include <fcntl.h>
#include <time.h>
#define MAXLEN 256
int main(int argc, unsigned char *argv[])
{
int f1, f2, n, i, read_total = 0, write_total = 0;
unsigned char buf[MAXLEN] = {0};
time_t start_time, end_time;
if (argc != 3) {
printf("Format: cp argv[1] to argv[2]\n");
return -1;
}
if ((f1 = open(argv[1], O_RDONLY, 0)) < 0) {
printf("Failed opening file %s\n", argv[1]);
return -2;
}
if ((f2 = creat(argv[2], 0666)) < 0) {
printf("Filed creating file %s\n", argv[2]);
return -3;
}
start_time = time(NULL);
while ((n = read(f1, buf, MAXLEN)) > 0) {
read_total += n;
printf("Write bytes: %d\n", n);
for (i = 0; i < n; i++)
printf("%02X ", buf[i]);
printf("\nEnd of write bytes\n");
printf("Write bytes: %d\n", n);
if (write(f2, buf, n) != n) {
printf("Failed writing file %s\n", argv[2]);
return -4;
}
write_total += n;
for (i = 0; i < n; i++)
printf("%02X ", buf[i]);
printf("\nEnd of read bytes\n");
printf("Write bytes: %d\n", n);
if (read_total != write_total) {
break;
}
}
end_time = time(NULL);
printf("used time: %d\n", end_time - start_time);
printf("write bytes: %d, read bytes: %d\n", write_total, read_total);
write(1, "Copy successfully\n", 19);
close(f1);
close(f2);
return 0;
}
这段代码用的是操作系统底层的read和write的函数,实际读取的文件我随便选择了一个pdf文件,以保证各种字符组合都能被测试到。但在实际读取的时候,往往总是在读到200~600字节的时候就终止读取了。经过一个字符一个字符的比对,我发现总是在读取到0x1A的时候:
上面的是原始文件
这个是读取到的文件
可以看到,确实是在遇到了1a这个字符的时候读取停止了。查了资料之后发现,这是因为原始的原先DOS系统会把0x1A当作EOF文件结尾符,之后的微软也继承了dos的内核,所以也会存在这个问题。虽然有文章表明后续的windows系统已经解决了这个问题,但是就我做实验得到的结果来看依然会有这种问题(我使用的是安装在Windows10系统上的mingw编译器)。本来想使用VS试一下会不会用有同样的问题,但是微软的vs对这种原始函数支持性太差了,程序放上去后各种deprcated的错误,遂放弃了在vs上实验的想法,直接跳到linux上实验。最终发现这种情况在linux上并不存在,我试了一个300MB的PDF,几乎瞬间就复制完了,而且也可以正常打开。由此可见Linux系统并不会将某个ascii码当作EOF文件结尾符来对待。在网上查资料后发现这篇博文对该机制有完整的解释:
https://cloud.tencent.com/developer/article/1527939?from=information.detail.linux%E5%88%A4%E6%96%AD%E6%96%87%E4%BB%B6%E8%AF%BB%E5%8F%96%E7%BB%93%E6%9D%9F%E7%AC%A6
总结该篇博文就是,文件中是不会存储文件结尾符的,实际的EOF是当文件意外终止或者文件结束的时候代码抛出的标识符。因此正常来说是不应当在读取文件的时候读到这个值的。有理由怀疑windows的相应机制存在一些问题。
此外在《C Programming language》一书中有如下对EOF的代码解释:
int _fillbuf(FILE *fp)
{
int bufsize;
if ((fp->flag & (_READ | _EOF | _ERR) != _READ)
return EOF;
bufsize = (fp->flag & _UNBUF) ? 1 : BUFSIZE;
if (fp->base == NULL)
if ((fp->base = (char *)malloc(bufsize)) == NULL)
return EOF;
fp->ptr = fp->base;
fp->cnt = read(fp->fd, fp->ptr, bufsize);
if (--fp->cnt < 0) {
if (fp->cnt == -1)
fp->flag |= _EOF;
else
fp->flag |= _ERR;
fp->cnt = 0;
return EOF;
}
return (unsigned char) *fp->ptr++;
}
尝试寻找解决办法
在该篇博客中作者提出了两个解决办法:
https://blog.csdn.net/apollon_krj/article/details/80020936
文中作者提出了两种解决办法,其中一种是通过fopen函数来完成该功能,另一种是通过每次遇到0x1A后判断是否到达文件末尾。虽然最终没有解决我想要达成的目的,但文章中提到的fopen函数可以正常读取文件,仍对我未来继续研究该问题给出了一些希望,最后给出一个fopen函数实现方法:
https://blog.csdn.net/iu_81/article/details/2349805
文章中最后给出了一个类似fopen的函数的实现,虽然是微软定义实现,但是仍可以发现是有特殊的符号用于标识是否以二进制读取文件。说明确实有相应的变量定义允许我们可以以二进制的方法文件。