c++ - 文件 I/O 二进制动态数组崩溃
问题描述
#include iostream
#include cmath
#include fstream
#include cstdlib
#include string
using namespace std;
class Device {//Input and store Device Description and Serial Numbers
protected:
string serial_number;
string device_description;
public:
Device() {
serial_number = ("6DCMQ32");
device_description = ("TheDell");
}
};
class Test {//Input and store Test Description, recent day, and month;
Calculate the next day
protected:
string Test_Description;
static int recent_month, recent_day, recent_year, new_month;
static int nmonth, next_month, next_day, next_year, max_day;
public:
Test() {
Test_Description = ("Virtual");
}
static void getMonth(ostream & out) {//Calculates the next/new month
next_month = recent_month + nmonth;
new_month = next_month % 12;
if (next_month >= 12) {
cout << "The next Date: " << new_month << " / ";
}
else {
out << "The next Date: " << next_month << " / ";
}
}
static void getDay(ostream & out) { //Calculates day of next month
if (new_month == 4 || new_month == 6 || new_month == 9 || new_month == 11) {
max_day = 30;
}
else if (new_month == 2) {
max_day = 29;
}
else {
max_day = 31;
}
if (recent_day > max_day) {
out << max_day << " / ";
}
else {
out << recent_day << " / ";
}
}
static void getYear(ostream & out) {// Calculate the year of next month
next_year = recent_year + next_month;
if (next_year >= 12) {
out << recent_year + (next_month / 12) << endl;
}
else {
out << next_year << endl;
}
}
static void getDate(ostream & out) {// Collects the output of each element of next date
getMonth(out), getDay(out), getYear(out);
}
};
int Test::recent_month;
int Test::recent_day;
int Test::recent_year;
int Test::new_month;
int Test::nmonth;
int Test::next_month;
int Test::next_day;
int Test::next_year;
int Test::max_day;
class Lab : public Device, public Test {
protected:
static int n;
public:
friend istream & operator>>(istream & in, Lab & lab) {// Inputs
cout << "Enter Device Desciption and Serial Number: ";
getline(cin, lab.device_description);
getline(cin, lab.serial_number);
cout << "Enter Test Desciption: ";
getline(cin, lab.Test_Description);
cout << "Enter the Number of months: ";
in >> nmonth;
cout << "Enter the Most Recent Date(mm/dd/yyyy): ";
in >> recent_month >> recent_day >> recent_year;
return in;
}
friend ostream & operator<<(ostream & out, Lab & lab) {//Outputs everything in Device Class
out << lab.device_description << endl;
out << lab.serial_number << endl;
out << lab.Test_Description << endl;
getDate(out);
return out;
}
static void getN() {
cout << "Enter the number of devices: ";
cin >> n;
}
static void getWrite() {
Lab *obj = new Lab[n];
if (obj == 0) {
cout << "Memory Error";
exit(1);
}
for (int i = 0; i<n; i++) {
cin >> obj[i];
cout << endl;
}
ofstream myfile("Device.dat", ios::binary);
myfile.write((char*) obj, n * sizeof(Lab));
delete[] obj;
}
static void getRead() {
ifstream file2("Device.dat", ios::binary);
Lab *obj2 = new Lab[n];
if (obj2 == 0) {
cout << "Memory Error";
exit(1);
}
file2.read((char*) obj2, n * sizeof(Lab));
for (int i = 0; i < n; i++) {
cout << obj2[i];
cout << endl;
}
delete[] obj2;
}
};
int Lab::n;
void main() {
Lab L;
L.getN();
L.getWrite();
L.getRead();
getchar();
getchar();
system("pause");
}
输出值后程序不断崩溃
目的:输入序列号、设备描述、测试描述、最近日期和两次测试的月数,输入设备下一次测试日期的月数。最后,必须通过让用户输入序列号和下一个日期来搜索程序,如果这两个有效,则列出设备中的所有内容。
我正在使用 Microsoft Visual Studios 2017
解决方案
std::string
不幸的是,数据结构过于复杂,无法简单地写入文件。最简单的情况是,astring
是一个指向字符数组的指针和一个存储数组长度的整数。当你写一个指向文件的指针时,你写的是地址,而不是地址处的数据。当您阅读string
后面的内容时,很有可能您最终会得到一个指向程序不拥有的内存的陈旧地址以及许多崩溃的坏处。更糟糕的是,如果回读地址指向程序中确实存在的东西。这些通常不会立即崩溃并导致您远离实际的错误,因为当您责备和调试错误的代码时,腐败者正坐在另一段代码中,完全自鸣得意地吹口哨。无论哪种方式访问尚未分配给指针的内存都会调用Undefined Behavior和 UB 任何事情都可能发生。不要指望崩溃或一致性。
通常,您会使用operator <<
重载将结构序列化到文件中,而不是尝试二进制写入。如果必须进行二进制写入,则需要创建一个协议来描述必须如何写入和读取数据。
该协议将是一组函数,可以将更简单的数据类型与其文件等价物相互转换。
写 a 的典型方法string
是先写 的长度,string
然后写 的内容string
。就像是
uint32 len = str.length(); //fixed width length
len = htonl(len); // fixed endian
myfile.write((char*) &len, sizeof(len)); //write length
myfile.write(str.data(), str.length()); //write string
和阅读
uint32 len; //fixed width length
myfile.read((char*) &len, sizeof(len)); //read length
len = ntohl(len); // unfix endian
std::string str(len, ' '); //string and allocate size
myfile.write(str.data(), len); //read string C++17 or
//myfile.write(&str[0], len); //read string before C++17
将这些捆绑在内部函数中,您就已经开始使用您的协议了。为您需要存储的其他数据类型添加功能。
然后,这些函数被转换更大数据类型的函数调用,直到你达到你需要编写的最复杂的结构。对于数组,使用循环。如果您有可变大小的长度,请在长度前加上string
.
旁注:在读取或写入数字时,必须注意确保数字是已知的固定大小。int
,例如, can be any size 16 bits or greater so long at it's not larger than
long . You don't necessarily know that the file reader will be using the same sized
int`,因此您应该更喜欢一个足够大的Fixed Width Integer来存储所需的值。不同的计算机也可以以不同的顺序存储它们的二进制信息。这称为字节顺序或字节序。确保每个人都使用相同的字节序。