C++ cout 腐败
C++ cout corruption
我正在使用 ifstream 读取文件 header。
编辑:我被要求放置完整的最小程序,所以就在这里。
#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);
然后我使用一个结构来存储 header 个项目
实际结构比这更长。我缩短了它,因为我不需要这个问题的整个结构。
无论如何,当我阅读结构时,我会做一些检查以查看我得到了什么。这部分没问题。
不出所料,我的 cout 显示标签、针迹、颜色计数没有问题。
问题是,如果我想在读取 header 之后再执行一次 cout,我的输出就会损坏。例如,如果我将以下几行放在上面的代码之后,例如
我没有看到标签、针迹和颜色计数,而是看到奇怪的符号和损坏的输出。有时你可以看到 h.label 的输出,有一些损坏,但标签被 Stitches 覆盖了。有时带有奇怪的符号,但有时带有来自上一个 cout 的文本。我认为要么结构中的数据被破坏,要么 cout 输出被破坏,我不知道为什么。 header 越长,问题就越明显。我真的很想在 header 结束时执行所有操作,但如果我这样做,我会看到一团糟,而不是应该输出的内容。
我的问题是为什么我的 cout 会损坏?
使用数组存储字符串是危险的,因为如果分配20个字符来存储标签,而标签恰好是20个字符长,那么就没有空间存储NUL(0)终止字符。一旦字节存储在数组中,就没有什么可以告诉期望 null-terminated 字符串的函数(如 cout)字符串的末尾在哪里。
您的标签有 20 个字符。这足以存储字母表的前 20 个字母:
ABCDEFGHIJKLMNOPQRST
但这不是 null-terminated 字符串。这只是一个字符数组。事实上,在内存中,T
之后的字节将是下一个字段的第一个字节,恰好是您的 11 字符 st
数组。假设这 11 个字符是:abcdefghijk
.
现在内存中的字节如下所示:
ABCDEFGHIJKLMNOPQRSTabcdefghijk
无法分辨 label
结束和 st
开始的位置。当您将指针传递给按照约定被解释为 null-terminated 字符串的数组的第一个字节时,实现将愉快地开始扫描,直到它找到一个空终止字符 (0)。其中,在结构的后续重用中,它可能不会!存在溢出缓冲区的严重风险(读取超过缓冲区的末尾),甚至可能到达虚拟内存块的末尾,最终导致访问冲突/分段错误。
当您的程序首先 运行 时,header 结构的内存全为零(因为您使用 {} 进行了初始化),因此在从磁盘读取标签字段后, T
已经为零,因此您的第一个 cout 工作正常。 st[0]
处恰好有一个终止空字符。然后,当您从磁盘读取 st
字段时,您会覆盖它。当您再次返回输出 label
时,终止符消失了,并且 st
的某些字符将被解释为属于字符串。
要解决此问题,您可能需要使用不同的、更实用的数据结构来存储字符串,以便使用方便的字符串函数。并使用原始 header 结构来表示文件格式。
您仍然可以使用固定大小的缓冲区将数据从磁盘读入内存,这只是为了暂存目的(将其放入内存),然后将数据存储到使用 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);
值得一提的是,您还可以选择在读取文件时保留原始结构,而不是将原始字符数组复制到 std::strings。但是你必须确定当你想使用数据时,你总是计算字符串的长度并将其传递给将这些缓冲区作为字符串数据处理的函数。 (与我的 arrayToString
助手所做的类似。)
我正在使用 ifstream 读取文件 header。 编辑:我被要求放置完整的最小程序,所以就在这里。
#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);
然后我使用一个结构来存储 header 个项目
实际结构比这更长。我缩短了它,因为我不需要这个问题的整个结构。 无论如何,当我阅读结构时,我会做一些检查以查看我得到了什么。这部分没问题。
不出所料,我的 cout 显示标签、针迹、颜色计数没有问题。 问题是,如果我想在读取 header 之后再执行一次 cout,我的输出就会损坏。例如,如果我将以下几行放在上面的代码之后,例如
我没有看到标签、针迹和颜色计数,而是看到奇怪的符号和损坏的输出。有时你可以看到 h.label 的输出,有一些损坏,但标签被 Stitches 覆盖了。有时带有奇怪的符号,但有时带有来自上一个 cout 的文本。我认为要么结构中的数据被破坏,要么 cout 输出被破坏,我不知道为什么。 header 越长,问题就越明显。我真的很想在 header 结束时执行所有操作,但如果我这样做,我会看到一团糟,而不是应该输出的内容。
我的问题是为什么我的 cout 会损坏?
使用数组存储字符串是危险的,因为如果分配20个字符来存储标签,而标签恰好是20个字符长,那么就没有空间存储NUL(0)终止字符。一旦字节存储在数组中,就没有什么可以告诉期望 null-terminated 字符串的函数(如 cout)字符串的末尾在哪里。
您的标签有 20 个字符。这足以存储字母表的前 20 个字母:
ABCDEFGHIJKLMNOPQRST
但这不是 null-terminated 字符串。这只是一个字符数组。事实上,在内存中,T
之后的字节将是下一个字段的第一个字节,恰好是您的 11 字符 st
数组。假设这 11 个字符是:abcdefghijk
.
现在内存中的字节如下所示:
ABCDEFGHIJKLMNOPQRSTabcdefghijk
无法分辨 label
结束和 st
开始的位置。当您将指针传递给按照约定被解释为 null-terminated 字符串的数组的第一个字节时,实现将愉快地开始扫描,直到它找到一个空终止字符 (0)。其中,在结构的后续重用中,它可能不会!存在溢出缓冲区的严重风险(读取超过缓冲区的末尾),甚至可能到达虚拟内存块的末尾,最终导致访问冲突/分段错误。
当您的程序首先 运行 时,header 结构的内存全为零(因为您使用 {} 进行了初始化),因此在从磁盘读取标签字段后, T
已经为零,因此您的第一个 cout 工作正常。 st[0]
处恰好有一个终止空字符。然后,当您从磁盘读取 st
字段时,您会覆盖它。当您再次返回输出 label
时,终止符消失了,并且 st
的某些字符将被解释为属于字符串。
要解决此问题,您可能需要使用不同的、更实用的数据结构来存储字符串,以便使用方便的字符串函数。并使用原始 header 结构来表示文件格式。
您仍然可以使用固定大小的缓冲区将数据从磁盘读入内存,这只是为了暂存目的(将其放入内存),然后将数据存储到使用 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);
值得一提的是,您还可以选择在读取文件时保留原始结构,而不是将原始字符数组复制到 std::strings。但是你必须确定当你想使用数据时,你总是计算字符串的长度并将其传递给将这些缓冲区作为字符串数据处理的函数。 (与我的 arrayToString
助手所做的类似。)