C++使用ofstream保存TGA文件,第二次调用相同函数生成的文件更大是什么原因?
编辑:提问17分钟后问题已解决。
问题原因:我没有以二进制模式打开文件,Windows系统在非二进制模式下处理文件时会额外添加字节。
问题背景
我编写的SaveToFile函数代码如下,其中counter变量仅用于调试,无实际业务功能:
template<uint32_t ArrayLength> void SaveToFile(std::shared_ptr<std::array<uint8_t, ArrayLength>> TGA, std::string name) { std::ofstream outputFile; outputFile.open("hardcoded folder path because I am lazy" + name + ".tga"); int counter = 0; for (uint8_t& pixel : *TGA) { outputFile << pixel; counter++; } outputFile.close(); }
连续两次调用该函数的代码如下:
std::shared_ptr<std::array<uint8_t, 14422>> TGA = sim.GetTGAByteList(); SaveToFile(TGA, "test"); //sim.Step(16); sim.Draw(); // 其余Draw调用已注释 TGA = sim.GetTGAByteList(); assert(TGA->size()==14422); SaveToFile(TGA, "test2");
TGA数组的大小固定为14422,sim.Draw()仅修改TGA数组内存储的数据,不会改变数组长度,这一点也可从后续的assert断言得到验证。用counter变量调试时,逐行步进函数发现每次循环都刚好执行14422次,每次写入1字节。
异常现象
第一次保存的文件大小为14422字节,第二次保存的文件却达到了14583字节。就算传入全新的shared_ptr调用SaveToFile也无法解决该问题。调用sim.Draw()的次数越多,生成的文件就越大,但使用的是固定大小的数组,理论上不应该出现该问题,这些额外的字节是从哪来的?
如果注释掉sim.Draw()调用,无论怎么操作都无法复现该问题。
以下是Draw()函数的实现,其中pixel_data对应TGA数组的最后14400字节:
void Draw(uint8_t configflags = 0) { if ((configflags & 0b00000001) == 0) { for (uint32_t i = 0; i < (*pixel_data).size(); i++) { uint64_t sum_of_neighbours = 0; bool left_edge = i % texture_width == 0; bool right_edge = i % texture_width == texture_width - 1; bool top_edge = i < texture_width; bool bottom_edge = i >= (texture_width * (texture_height - 1)); if (!top_edge && !left_edge) //top left sum_of_neighbours += (*pixel_data)[i - texture_width - 1]; if (!top_edge) //top sum_of_neighbours += (*pixel_data)[i - texture_width] * 2; if (!top_edge && !right_edge) //top right sum_of_neighbours += (*pixel_data)[i - texture_width + 1]; if (!bottom_edge && !left_edge) //bottom left sum_of_neighbours += (*pixel_data)[i + texture_width - 1]; if (!bottom_edge) //bottom sum_of_neighbours += (*pixel_data)[i + texture_width] * 2; if (!bottom_edge && !right_edge) //bottom right sum_of_neighbours += (*pixel_data)[i + texture_width + 1]; if (!left_edge) //left sum_of_neighbours += (*pixel_data)[i - 1] * 2; if (!right_edge) //right sum_of_neighbours += (*pixel_data)[i + 1] * 2; //self sum_of_neighbours += (*pixel_data)[i] * 4; (*pixel_data)[i] = sum_of_neighbours / 16; } } if ((configflags & 0b00000010) == 0) { for (Agent& agent : agents) { uint32_t index = floor(agent.position.x); index += floor(agent.position.y) * texture_width; (*pixel_data)[index] = -1; } } }
就算把SaveToFile()中的循环硬编码为仅执行14422次,还是会生成大小错误的文件,修改保存的文件名也无法解决问题。
问题原理与修复方案
Windows系统下文件流默认以文本模式打开,此时流会自动将字节0x0A(换行符\n)替换为0x0D 0x0A(回车换行\r\n)的双字节序列,每遇到一次就多写入1字节。第一次生成的TGA数据中没有0x0A字节,所以文件大小和数组大小一致;调用Draw()之后数组内生成了0x0A字节,调用次数越多出现的0x0A越多,最终文件大小就会超出数组原始大小。
修复方法为打开文件时指定std::ios::binary二进制模式,修改open调用的代码即可:
outputFile.open("hardcoded folder path because I am lazy" + name + ".tga", std::ios::binary | std::ios::out);
二进制模式下流不会对写入的字节做任何转换,写入的字节数和数组大小会完全一致。
内容的提问来源于stack exchange,提问作者Jac O' B GmbH und co. KG

