我正在使用ifstream读取文件头。 编辑:我被要求放完整的最小程序,所以就在这里。
#include <iostream>
#include <fstream>
using namespace std;
#pragma pack(push,2)
struct Header
{
char label[20];
char st[11];
char co[7];
char plusXExtends[9];
char minusXExtends[9];
char plusYExtends[9];
};
#pragma pack(pop)
int main(int argc,char* argv[])
{
string fileName;
fileName = "test";
string fileInName = fileName + ".dst";
ifstream fileIn(fileInName.c_str(), ios_base::binary|ios_base::in);
if (!fileIn)
{
cout << "File Not Found" << endl;
return 0;
}
Header h={};
if (fileIn.is_open()) {
cout << "\n" << endl;
fileIn.read(reinterpret_cast<char *>(&h.label), sizeof(h.label));
cout << "Label: " << h.label << endl;
fileIn.read(reinterpret_cast<char *>(&h.st), sizeof(h.st));
cout << "Stitches: " << h.st << endl;
fileIn.read(reinterpret_cast<char *>(&h.co), sizeof(h.co));
cout << "Colour Count: " << h.co << endl;
fileIn.read(reinterpret_cast<char *>(&h.plusXExtends),sizeof(h.plusXExtends));
cout << "Extends: " << h.plusXExtends << endl;
fileIn.read(reinterpret_cast<char *>(&h.minusXExtends),sizeof(h.minusXExtends));
cout << "Extends: " << h.minusXExtends << endl;
fileIn.read(reinterpret_cast<char *>(&h.plusYExtends),sizeof(h.plusYExtends));
cout << "Extends: " << h.plusYExtends << endl;
// This will output corrupted
cout << endl << endl;
cout << "Label: " << h.label << endl;
cout << "Stitches: " << h.st << endl;
cout << "Colour Count: " << h.co << endl;
cout << "Extends: " << h.plusXExtends << endl;
cout << "Extends: " << h.minusXExtends << endl;
cout << "Extends: " << h.plusYExtends << endl;
}
fileIn.close();
cout << "\n";
//cin.get();
return 0;
}
ifstream fileIn(fileInName.c_str(), ios_base::binary|ios_base::in);
然后我使用一个结构来存储标题项
实际结构比这更长。我把它缩短了,因为我不需要整个结构来回答这个问题。 无论如何,当我阅读该结构时,我会发出提示以了解自己正在得到什么。这部分很好。
按预期,我的提示显示标签,针迹,颜色计数没有问题。 问题是,如果我想在读取标题后再执行另一个cout,则输出中会损坏。例如,如果我在上面的代码之后放置以下行,例如
我没有看到标签,针迹和颜色计数,而是得到了奇怪的符号和损坏的输出。有时,您会看到h.label的输出,但有一些损坏,但是标签被针迹覆盖了。有时带有奇怪的符号,但有时带有来自上一个提示的文本。我认为要么结构中的数据已损坏,要么cout输出已损坏,但我不知道为什么。标头越长,问题越明显。我真的很想在标头末尾进行所有提示,但是如果这样做,我会看到一团糟,而不是应该输出的内容。
我的问题是为什么我的服装被损坏?
答案 0 :(得分:2)
使用数组存储字符串是危险的,因为如果分配20个字符来存储标签,并且标签恰好长20个字符,则没有空间存储NUL(0)终止字符。一旦字节存储在数组中,就没有什么可告诉函数期望以null终止的字符串(例如cout)了,字符串的结尾在哪里。
您的标签有20个字符。这足以存储字母表的前20个字母:
ABCDEFGHIJKLMNOPQRST
但这不是一个以空字符结尾的字符串。这只是一个字符数组。实际上,在内存中,T
之后的字节将是下一个字段的第一个字节,恰好是您的11个字符的st
数组。假设这11个字符是:abcdefghijk
。
现在,内存中的字节看起来像这样:
ABCDEFGHIJKLMNOPQRSTabcdefghijk
无法判断label
在哪里结束,st
在哪里开始。当您将指针传递到旨在按照约定解释为以空值终止的字符串的数组的第一个字节时,该实现将很乐意开始扫描,直到找到一个以空值终止的字符(0)。在随后的结构重用中,可能不会!存在严重的缓冲区溢出风险(读取超出缓冲区末尾),甚至有可能超出虚拟内存块的末尾,最终会导致访问冲突/分段错误。
当程序第一次运行时,标头结构的内存全为零(因为您用{}进行了初始化),因此从磁盘读取标签字段后,T
之后的字节已经为零,因此您的第一个指令正常工作。 st[0]
恰好有一个终止的空字符。然后,当您从磁盘读取st
字段时,将覆盖此内容。当您再次返回输出label
时,终止符消失了,st
的某些字符将被解释为属于字符串。
要解决此问题,您可能希望使用其他更实用的数据结构来存储字符串,以便使用方便的字符串功能。并使用原始标头结构来表示文件格式。
您仍然可以使用固定大小的缓冲区将数据从磁盘读取到内存中,这只是出于暂存目的(将其存储到内存中),然后将数据存储到使用std :: string变量的其他结构中,以方便使用和以后由您的程序使用。
为此,您需要以下两个结构:
#pragma pack(push,2)
struct RawHeader // only for file IO
{
char label[20];
char st[11];
char co[7];
char plusXExtends[9];
char minusXExtends[9];
char plusYExtends[9];
};
#pragma pack(pop)
struct Header // A much more practical Header struct than the raw one
{
std::string label;
std::string st;
std::string co;
std::string plusXExtends;
std::string minusXExtends;
std::string plusYExtends;
};
阅读第一个结构后,您将通过分配变量来传输字段。这是一个帮助函数。
#include <string>
#include <string.h>
template <int n> std::string arrayToString(const char(&raw)[n]) {
return std::string(raw, strnlen_s(raw, n));
}
在您的职能中:
Header h;
RawHeader raw;
fileIn.read((char*)&raw, sizeof(raw));
// Now marshal all the fields from the raw header over to the practical header.
h.label = arrayToString(raw.label);
h.st = arrayToString(raw.st);
h.st = arrayToString(raw.st);
h.co = arrayToString(raw.co);
h.plusXExtends = arrayToString(raw.plusXExtends);
h.minusXExtends = arrayToString(raw.minusXExtends);
h.plusYExtends = arrayToString(raw.plusYExtends);
值得一提的是,您还可以选择保留原始结构,并且在读取文件时不将原始char数组复制到std :: strings。但是您必须确定,当您要使用数据时,始终要计算字符串的长度并将其传递给将这些缓冲区作为字符串数据处理的函数。 (类似于我的arrayToString
助手所做的事情。)