C语言fread/fwrite填坑记
坑的描述
用fwrite把数据写入文件,再用fread读取,发现后半部分的数据可能是错的。
原因:原本要写入文件的数据中,有0x0A,如果用的是文本模式打开的文件流,在windows下0x0A会被转换为0x0D和0x0A
其实windows下的git bash每次git add后都有类似的提示,只是一直没太注意:

先说结论
用fread或fwrite的时候,如果是要写入字符,那么打开的文件、读取的文件,用字符模式(w和r)
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数组等,则一定要用二进制模式打开文件(wb和rb)(尽管在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)。
The fwrite function writes up to count items, of size length each, from buffer to the output stream. The file pointer associated with stream (if there is one) is incremented by the number of bytes actually written. If stream is opened in text mode, each linefeed is replaced with a carriage-return - linefeed pair. The replacement has no effect on the return value.
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;
}

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

发现第5848个元素是错误的。
通过分别设定字符模式和二进制模式来写入文件,看到了差异:

第一次出现差异的地方是0x5B60后的一个元素,0x5B60恰好是十进制下的23392,23392=5848 x 4, 4表示sizeof(float)
C语言fread/fwrite填坑记的更多相关文章
- UiAutomator2.0升级填坑记
UiAutomator2.0升级填坑记 SkySeraph May. 28th 2017 Email:skyseraph00@163.com 更多精彩请直接访问SkySeraph个人站点:www.sk ...
- Android项目开发填坑记-Fragment的onBackPressed
Github版 CSDN版 知识背景 Fragment在当前的Android开发中,有两种引用方式,一个是 Android 3.0 时加入的,一个是supportV4包中的.这里简称为Fragment ...
- Android项目开发填坑记-Fragment的onAttach
背景 现在Android开发多使用一个Activity管理多个Fragment进行开发,不免需要两者相互传递数据,一般是给Fragment添加回调接口,让Activity继承并实现. 回调接口一般都写 ...
- Android项目开发填坑记-so文件引发的攻坚战
故事的最初 我负责的项目A要求有播放在线视频的功能,当时从别人的聊天记录的一瞥中发现百度有相关的SDK,当时找到的是Baidu-T5Player-SDK-Android-1.4s,项目中Demo的so ...
- minikube windows hyperx填坑记
minikube windows hyperx填坑记 安装了一天半,还是没行,先放弃 开始 minikube start --vm-driver=hyperv --hyperv-virtual-swi ...
- 浅谈html5 video 移动端填坑记
这篇文章主要介绍了浅谈html5 video 移动端填坑记,小编觉得挺不错的,现在分享给大家,也给大家做个参考.一起跟随小编过来看看吧 本文介绍了html5 video 移动端填坑记,分享给大家,具体 ...
- Java web 开发填坑记 2 -如何正确的创建一个Java Web 项目
转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/72566261 本文出自[赵彦军的博客] Java web 开发填坑记 1-如何正确 ...
- Appium+python自动化(十三)- 输入中文 - 一次填坑记(超详解)
简介 无论你在哪里,在做什么都会遇到很多坑,这些坑有些事别人挖的,有些是自己挖的.别人挖的叫坑人,自己挖的叫自杀,儿子挖的叫坑爹.因此在做app自动化道路上也不会是一帆风顺的,你会踩很多坑,这些坑和你 ...
- Cloudera Manager 5.9 和 CDH 5.9 离线安装指南及个人采坑填坑记
公司的CDH早就装好了,一直想自己装一个玩玩,最近组了台电脑,笔记本就淘汰下来了,加上之前的,一共3台,就在X宝上买了CPU和内存升级了下笔记本,就自己组了个集群. 话说,好想去捡垃圾,捡台8核16线 ...
随机推荐
- 其他综合-fdisk一键分区操作-无需脚本
fdisk一键操作分区-无需脚本(根据自己的实际环境操作) 为了让在系统里能够显示新添加的硬盘已知有两种操作方法 : 1.重启 2.输入echo "- - -" > /sys ...
- 自定义组件的properties和data
// components/epsoide/index.js Component({ /** * 组件的属性列表 */ properties: { index: { type: Number //va ...
- 【C/C++】Dijkstra算法的简洁实现
Dijkstra的实现有很多种,下面给出一种较为简洁和高效的实现,可以作为模板快速使用. 1. 使用邻接表存储图: 2. 使用标准STL的vector存储每个点的所有邻接边: 3. 使用pair记录当 ...
- GDOI2018游记&题解
day0 第一件事当然是去酒店入住+领一堆东西. 感觉酒店不错,而且离学校挺近的,走路10分钟不到,骑车5分钟就到了. 然后去学校吃饭.我们在教工饭堂吃饭,饭菜还不错,但是没有筷子差评. 吃完饭后找了 ...
- Django_RBAC_demo2 升级版权限控制组件
RBAC 升级版 预期要求 前端在无权限时不在提供操作标签 更改部分硬编码 实现更加精准的权限控制 未改动前的版本 在这里 ⬇ Django_rbac_demo 权限控制组件框架模型 具体更改 数据库 ...
- pymongo 操作
python 操作 mongoDB 模块 pymongo 安装方法 sudo pip3 install pymongo 操作步骤 1. 创建数据库连接对象 conn = pymonge.MomgoCl ...
- 04Vue.js路由系统
Vue.js路由系统: https://pizzali.github.io/2018/10/28/Vue.js%E8%B7%AF%E7%94%B1%E7%B3%BB%E7%BB%9F/
- 使用item来封装数据:
一.item和field类: 1.使用Item类: 创建了类Bookitem,然后就可以使用: 2.item_pipeline: 我们可以使用item_pipeline对爬取的数据进行处理. 步骤: ...
- 常用js方法整理(个人)
开头总要有点废话 今天想了下,还是分享下自己平时积累的一些实用性较高的js方法,供大家指点和评价.本想分篇介绍,发现有点画蛇添足.整理了下也没多少拿得出手的方法,自然有一些是网上看到的个人觉得很有实用 ...
- Aerospike-内存和硬盘混合存储的kv数据库
为什么会有Aerospike? Redis是一个纯内存型数据库,性能上没有多大问题. 但这又带来一个新问题,内存是很贵的,所以全内存的存储成本非常昂贵.为了节省成本,我们需要把一部分不经常用到的数据存 ...