I've looked at binary reading and writing objects in c++ but are having some problems. It "works" but in addition I get a huge output of errors/"info".
What I've done is
Person p2;
std::fstream file;
file.open( filename.c_str(), std::ios::in | std::ios::out | std::ios::binary );
file.seekg(0, std::ios::beg );
file.read ( (char*)&p2, sizeof(p2));
file.close();
std::cout << "Name: " << p2.name;
Person is a simple struct containing string name and int age. When I run the program it outputs "Name: Bob" since I have already made a program to write to a file (so the object is already in filename).
IN ADDITION to outputting the name it also outputs:
* glibc detected * program: double free og corruption (fastttop): ***
Backtrace:
...
Memory map:
...
Abort
Is the name string in the Person struct a character array or a STL string? You can't fill in an STL String by binary reading data over top of it, since the data format is not serializable (contains pointers)
It would be interesting to see how you write the information to file as well, as well as how the Person struct is built.
If you don't have any problem that the file is plain text, my suggestion would be to write to file using string::c_str() (which returns a const char*) as well as using itoa() or itoa_s() to get the integer as a char*.
You can also have one or several constructors in Person:
Person(const std::string& name, int age);
Person(const char* name, int age);
then, when you extract the data from the file you just call the constructor with that data.
Either p2.name is a char* and you are writing and reading the pointer value, not what is pointed by it. Or p2.name is a more complex type such as std::string which is using internaly pointers with the same problem.
Serializing classes often need more work than just dumping the memory representation.
You said you wrote the Person object to a file. Did you tried to use a dump tool to see if what you have inside the file is what you are expecting?
Also did you tried to instead of using string, use a ordinary char (as #bdk pointed out) ?
When you use binary IO, the size must be fixed. If you use STL string here, it would have a problem as the size of a STL string is arbitrary.
Related
I'm learning file handling in c++ from internet alone. I came across the read and write function. But the parameters they take confused me.
So, I found the syntax as
fstream fout;
fout.write( (char *) &obj, sizeof(obj) );
and
fstream fin;
fin.read( (char *) &obj, sizeof(obj) );
In both of these, what is the function of char*?
And how does it read and write the file?
The function fstream::read has the following function signature:
istream& read (char* s, streamsize n);
You need to cast your arguments to the correct type. (char*) tells the compiler to pretend &obj is the correct type. Usually, this is a really bad idea.
Instead, you should do it this way:
// C++ program to demonstrate getline() function
#include <iostream>
#include <string>
using namespace std;
int main()
{
string str;
fstream fin;
getline(fin, str); // use cin instead to read from stdin
return 0;
}
Source: https://www.geeksforgeeks.org/getline-string-c/
The usage of the char * cast with read and write is to treat the obj variable as generic, continuous, characters (ignoring any structure).
The read function will read from the stream directly into the obj variable, without any byte translation or mapping to data members (fields). Note, pointers in classes or structures will be replaced with whatever value comes from the stream (which means the pointer will probably point to an invalid or improper location). Beware of padding issues.
The write function will the entire area of memory, occupied by obj, to the stream. Any padding between structure or class members will also be written. Values of pointers will be written to the stream, not the item that the pointer points to.
Note: these functions work "as-is". There are no conversions or translations of the data. For example, no conversion between Big Endain and Little Endian; no processing of the "end of line" or "end of file" characters. Basically mirror image data transfers.
I am trying to append into file in binary mode but the logic below is not working.
For Pdf files,file is getting corrupted and for text files, it is adding some junk data in addition to my file contents.
My variable m_strReceivedMessage is of type std::string.
std::ofstream out(file, std::ios::binary | std::ios_base::app );
int i = sizeof(m_strReceivedMessage);
if (out.is_open()) {
// out.write(m_strReceivedMessage.c_str(), m_strReceivedMessage.size());
//out << m_strReceivedMessage;
out.write(reinterpret_cast<char *>(&m_strReceivedMessage), m_strReceivedMessage.size());
}
You're printing the memory of the std::string object, rather than the character buffer that it contains. To get a pointer to the character buffer, see the data() member function. Hint: The fact that you need to cast std::string* using reinterpret_cast<char*> is a dead giveaway that you're doing something very wrong.
Also, I'm not familiar with the PDF spec, but I suspect that it may possibly contain nul bytes. And depending on how you get your std::string, it's possible you may have missed any content after the first nul. std::vector<char> would be more appropriate way to store binary data.
All,
I'm still learning C++ but I have an issue in a project I'm tinkering with that I'm curious about. I've noticed that when I try to print the contents of a string that is a member of a class, I get the memory address instead of the contents. I understand that this is because the item is a pointer, but what I"m confused about is that I am using the -> operator to deference it.
Why can I evaluate the class member in an if statement using the -> operator to dereference it but when printing to a file string in the same manner I get the memory address instead?
An example is below:
Lets say I have a class called pClass with a member called m_strEmployeeName. As a side note (I don't know if it matters), the m_strEmployeeName value is CString rather than std::string, so there could be some unknown conversion issue possibly as well.
If I used the following simple code, I get a memory address.
std::ofstream file("testfile.text");
file << pClass->m_strEmployeeName;
file.close();
I get the same behavior with the following dereferencing method (which I would expect since -> is the same thing).
std::ofstream file("testfile.text");
file << (*pClass).m_strEmployeeName;
file.close();
Any idea on what I'm doing wrong?
It is because your CString class is actualy CStringW class wich contain wchar_t strings so std::ofstream not contain operator >> overload that support wchar_t* strings. To print CStringW class objects you may use this type of stream std::wofstream it recognize wchar_t* strings properly and output will be right.
std::wofstream file("testfile.text");
file << pClass->m_strEmployeeName;
file.close();
You may also create your program in multibyte character support. It can be specified in your project settings. But I suggest you to stay with UNICODE.
Try casting CString to a char pointer:
file << (LPCTSTR)pClass->m_strEmployeeName;
see: How to convert CString and ::std::string ::std::wstring to each other?
Note: This will only work if you have TCHAR defined as 8 bits. If you're using 16-bit UNICODE TCHAR, you'd have one more conversion.
Here is one way of doing the TCHAR conversion:
char c_str[1000];
size_t ret;
wcstombs_s(
&ret,
c_str,
sizeof(c_str),
(LPCTSTR)pClass->m_strEmployeeName,
pClass->m_strEmployeeName.GetLength()
);
std::ofstream file("testfile.text");
file << c_str;
file.close();
Useful if you need 8-bit ASCII file but have a UNICODE CString to work with.
So, I'm currently writing a line editor as a learning project on I/O, writing files, and the like. It is written in C++, and I am currently trying to write out to a file of the user's choosing. I have CLI arguments implemented, but I currently have no idea how to implement an in program way of specifying the file to write to.
char *filename;
if (argc >= 2){
filename = argv[1];
} else{
cout << "file>";
cin >> filename;
cin.ignore();
}
This works perfectly well when I use command line arguments; however, whenever I do not, as soon as I start the program, it Segmentation Faults. The place where I use the actual filename is in the save command:
void save(char filename[], int textlen, string file[]){
ofstream out(filename);
out << filestring(textlen, file);
out.close();
}
Which also works perfectly well. Is there any way you can help me? Full source code, for review, is up on https://github.com/GBGamer/SLED
The problem is that char* filename is just a pointer to some memory containing characters. It does not own any memory itself.
When you use the command line argument, the program handles storing that string somewhere, and you get a pointer to it. When you try to read using cin >> filename there isn't actually anywhere to store the read data.
Solution: Replace char* filename with std::string filename (and #include <string>).
Then to open the output file, you need a c-style string (null terminated char array). std::string has a function for this. You would write
std::ofstream out(filename.c_str());
^^^^^
Or, in fact, if you can use a recent compiler with c++11 features, you don't even need to use c_str(). A new std::ofstream constructor has been added to accept a std::string.
Your filename variable points to argv[1] when command line argument is provided, it does not need memory to be allocated but when going in else block, you have not allocated memory to filename. Its just a pointer.
Use malloc to assign filename some memory then take user input.
filename = (char *)malloc(sizeof(char)*(FILE_NAME_LENGTH+1))
These methods are supposed to save and load the entirety of the object they're associated with. When I compile the program under Linux through gcc, the save seems to work but it segfaults when loading. When I compile it under Windows through the Visual Studio compiler, it works like a dream. I am not sure what the differences are, but I've got a hunch that it involves some gcc oddity.
The two methods:
void User::SaveToFile()
{
ofstream outFile;
string datafile_name = username + "_data";
outFile.open(datafile_name.c_str(), ios::binary);
outFile.write((char*)this, sizeof(*this));
}
void User::LoadFromFile(string filename)
{
ifstream inFile;
inFile.open(filename.c_str(), ios::binary);
inFile.read((char*)this, sizeof(*this));
}
The declaration:
class User
{
private:
string username;
string realname;
string password;
string hint;
double gpa;
vector<Course> courses;
public:
double PredictGPA();
void ChangePassword();
void SaveToFile();
void LoadFromFile(string filename);
void SetUsername(string _username){username = _username;}
string GetUsername(){return username;}
void SetRealname(string _realname){realname = _realname;}
string GetRealname(){return realname;}
void SetPass(string _password){password = _password;}
string GetPass(){return password;}
void SetHint(string _hint){hint = _hint;}
string GetHint(){return hint;}
};
Your class User is not a POD type, its not a Plain Old Data type (as C structs are). You cannot just read and write its memory bitwise and expect it to work. Both string and vector are not PODs, they keep pointers to their dynamically allocated data. When reading those back, attempts to access invalid memory will result in a segfault. What's more, the contents of both the string and vector are not actually being saved at all, since they are not within the memory layout of the object (it may work sometimes with string with SBO, but its just but chance and still undefined to do it).
You would need a way to serialize and deserialize your class; your class can't magically become an object when you read it in like that.
Instead you would need to supply to functions that you call when loading/saving your class that store the class in some format of your choosing e.g. XML.
so instead of
outFile.write((char*)this, sizeof(*this));
have some member function to convert it to a string with some format that you easily can parse when you load it (or some binary format whatever you find easier), then save it.
outFile.write(this->myserialize(), mysize);
You can't write into string like that. For one thing it usually stores its data dynamically, i.e. not inside the object at all, and for another you shall not rely on any particular layout of it.
There are similar issues with vectors, and you don't appear to have considered endianness and padding at all.
Put simply, you're making assumptions that do not hold.
In general, do not mess with complex (non-POD) objects on the byte level. Serialise with some text format instead, using the objects' public member functions to extract and restore their state.
Have you considered JSON?
Things like strings etc may contain pointers - in which case your method can go horribly wrong.
You need to serialise the data - I.e. convert it to a series of bytes.
Then when reading the data you just read the bytes and then create the object from that. The new pointers will be correct.
If you stay with this route I would write the length of the string instead of null terminating it. Easier to allocated on loading. There is alot to consider in a binary format. Each field should have some type of ID so it can be found if in wrong spot or a different version of your program. Also at the beginning of your file write what endianess you are using and the size of your integers etc. Or decide a standard size and endianess for everything. I use to write code like this all the time for networking and file storage. There are much better modern approaches. Also consider using a buffer and creating Serialize() function.
Good modern alternatives include :SQLite3, XML, JSON
Untested Example:
class object
{
Load()
{
ifstream inFile;
int size;
inFile.open("filename", ios::binary);
inFile.read(&size, 4);
stringA.resize(size);
inFile.read(&stringA[0], size);
inFile.read(&size, 4);
stringB.resize(size);
inFile.read(&stringB[0], size);
inFile.close(); //don't forget to close your files
}
Save()
{
ofstream outFile;
int size;
outFile.open("filename", ios::binary);
size = stringA.size();
outFile.write(&size, 4);
outFile.write(&stringA[0], size);
size = stringB.size();
outFile.write(&size, 4);
outFile.write(&stringA[0], size);
outFile.close();
}
private:
std::string stringA
std::string stringB
};