前言
在 Unix 环境下进行文件的拷贝时遇到了一些问题,因此将两种方式都记录下来以备不时之需
一、通常拷贝方式
#include <stdio.h>
#include <stdlib.h>
int main() {
int i, ch;
FILE *fp1, *fp2;
char* from_file = "path\\to\\source\\file";
char* to_file = "path\\to\\save\\file";
fp1 = fopen(from_file, "r");
fp2 = fopen(to_file, "w");
if(fp1 != NULL && fp2 != NULL) {
while((ch = fgetc(fp1)) != EOF) {
fputc(ch, fp2);
}
} else {
printf("failed to copy file due to open error");
}
fclose(fp1);
fclose(fp2);
return 0;
}
我们可以看到通常拷贝模式简单地打开了文件并且一个字节一个字节地将源文件中内容拷贝到目标文件中,并通过 EOF
标识符判定是否文件结束,而问题就恰恰出在了这里。
二、有关 EOF
EOF
即 End Of File
,标志着文件的结束,但它并不存在于文件中,即我们并不是通过读到文件中的 EOF
标志来判断文件是否结束。
EOF
本身是定义于 stdio.h
中的宏,值为 -1
,相信看到这个值很多小伙伴应该就懂了,EOF
在拷贝的过程中相当于标志着读到的内容已经不在文件本身中了,通过报错实现了结束。但此时问题就来了 fgetc
返回的是 unsigned char
而 EOF
的值为 -1
即 0xff
,此时的判断根据编译结果或运行环境的不同将产生两种不同结果:
fgetc
结果始终为unsigned char
,EOF
将被视为正常文件内容即0xff
从而导致死循环fgetc
结果在与EOF
比较时被提升为int
型,EOF
正确识别并结束
作者最近正是遇到了第一种情况导致出现了很多问题,最后通过新的拷贝方式规避成功。
如果对比较时提升有疑虑的话可以试试以下代码
#include <stdio.h>
int main(void)
{
int i = -1;
signed char sc = 0xff;
unsigned char usc = 0xff;
printf ("Comparing %x with %x\n", i, sc);
if (i == sc) puts("i == sc");
else puts("i != sc");
putchar ('\n');
printf ("Comparing %x with %x\n", i, usc);
if (i == usc) puts("i == usc");
else puts("i != usc");
return 0;
}
/*
* Output
Comparing ffff with ffff <--- Notice this has been promoted
i == sc
Comparing ffff with ff
i != usc
*
*/
三、新的拷贝方式
问题是找到了,那么怎么解决呢?答案很简单, 使读取后超出的错误能被正常比较捕捉即可。
#include <stdio.h>
#include <stdlib.h>
int main() {
int i, ch;
size_t rs, ws;
FILE *fp1, *fp2;
char* from_file = "path\\to\\source\\file";
char* to_file = "path\\to\\save\\file";
fp1 = fopen(from_file, "rb");
fp2 = fopen(to_file, "wb");
if(fp1 != NULL && fp2 != NULL) {
unsigned char buff[1024];
do {
rs = fread(buff, 1, sizeof(buff), fp1);
if (rs) ws = fwrite(buff, 1, rs, fp2);
else ws = 0;
} while((rs >0) && (rs == ws));
} else {
printf("failed to copy file due to open error");
}
fclose(fp1);
fclose(fp2);
return 0;
}