坑的描述

fwrite把数据写入文件,再用fread读取,发现后半部分的数据可能是错的。

原因:原本要写入文件的数据中,有0x0A,如果用的是文本模式打开的文件流,在windows下0x0A会被转换为0x0D和0x0A

其实windows下的git bash每次git add后都有类似的提示,只是一直没太注意:

C语言fread/fwrite填坑记-LMLPHP

先说结论

freadfwrite的时候,如果是要写入字符,那么打开的文件、读取的文件,用字符模式(wr

FILE* fin = fopen("filename", "w");
fread(buf, sizeof(char)*num_elem, 1, fin);
fclose(fin);
FILE *fout = fopen("filename", "r");
fwrite(buf, sizeof(char)*num_elem, 1, fout);
fclose(fout);

如果是要写入非字符的数据,例如float数组、int数组等,则一定要用二进制模式打开文件(wbrb)(尽管在linux和mac下你的结果也许一直没问题,但是保不准到了windows下会出错):

FILE* fin = fopen("filename", "wb");
fread(buf, sizeof(float)*num_elem, 1, fin);
fclose(fin);
FILE *fout = fopen("filename", "rb");
fwrite(buf, sizeof(float)*num_elem, 1, fout);
fclose(fout);

原因:字符模式打开的文件,在windows下,遇到0x0A进行写入(也就是\n)会替换为0x0D和0x0A(分别是\r\n)。

ref:https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/fwrite?view=vs-2017

举例细说

读取图像,通常用opencv,但是考虑到arm上用opencv过于庞大,考虑在pc上把图像的数据读取出来,然后整理下顺序,再用fwrite保存。后面在arm上直接fread就行了,避开了opencv。

但在具体实现的时候发现,fwrite后再fread,只有前面一部分数据是正确的!原因如上面说的,保存到文件的是float数组,但是打开文件的模式错误的设定为了字符模式,而不是二进制模式。

#include <stdio.h>
#include <stdlib.h> #include <iostream>
#include <string> #include <vector>
#include <opencv2/opencv.hpp> using namespace std;
using namespace cv; int main() {
string im_pth = "../cat_227.jpg";
IplImage* img = cvLoadImage(im_pth.c_str(), CV_LOAD_IMAGE_COLOR); int iImgChnl = 3;
int iImgHgt = 227;
int iImgWth = 227;
int num_elem = iImgChnl * iImgHgt * iImgWth;
float* pfImgData;
pfImgData = (float*)malloc(sizeof(float)*num_elem); float* f_input_data_b = (float*)malloc(sizeof(float)*iImgHgt*iImgWth);
float* f_input_data_g = (float*)malloc(sizeof(float)*iImgHgt*iImgWth);
float* f_input_data_r = (float*)malloc(sizeof(float)*iImgHgt*iImgWth); for (int i = 0; i < num_elem; i += 3) {
f_input_data_b[i / 3] = (float)(unsigned char)(img->imageData[i]);
f_input_data_g[i / 3] = (float)(unsigned char)(img->imageData[i + 1]);
f_input_data_r[i / 3] = (float)(unsigned char)(img->imageData[i + 2]);
} for (int i = 0; i < iImgHgt*iImgWth; i++) {
pfImgData[i] = f_input_data_b[i];
}
for (int i = 0; i < iImgHgt*iImgWth; i++) {
pfImgData[i + iImgHgt*iImgWth] = f_input_data_g[i];
}
for (int i = 0; i < iImgHgt*iImgWth; i++) {
pfImgData[i + 2*iImgHgt*iImgWth] = f_input_data_r[i];
}
int ret; string save_pth = "../cat_227.fread_float.w";
FILE* fout = fopen(save_pth.c_str(), "w");
ret = fwrite((void*)pfImgData, sizeof(float), num_elem, fout);
fclose(fout); printf("--- pfImgData[5847]=%f, pfImgData[5848]=%f\n", pfImgData[5847], pfImgData[5848]); //--------------------------------------------------
float* tuopan = (float*)malloc(sizeof(float)*num_elem);
FILE* fin = fopen(save_pth.c_str(), "rb");
ret = fread((void*)tuopan, sizeof(float), num_elem, fin);
fclose(fin); printf("--- tuopan[5847]=%f, tuopan[5848]=%f\n", tuopan[5847], tuopan[5848]); printf("--- check here---\n"); return 0;
}

C语言fread/fwrite填坑记-LMLPHP

测试环境:VS2013 update5, win32/x64 debug/release模式

调试结果:

C语言fread/fwrite填坑记-LMLPHP

发现第5848个元素是错误的。

通过分别设定字符模式和二进制模式来写入文件,看到了差异:

C语言fread/fwrite填坑记-LMLPHP

第一次出现差异的地方是0x5B60后的一个元素,0x5B60恰好是十进制下的23392,23392=5848 x 4, 4表示sizeof(float)

04-15 17:06