Confused with fstream and pointers - c++

I would like to write a function which takes an fstream, processes it, and fills information in a struct I supply in the second argument.
My problem is that I am confused how to use pointers and fstreams as I get debug errors:
Access violation writing location
0xcccccccc.
Here is the main function:
int main()
{
keyframe_struct kfstruct;
string ifile = "filename";
ifstream fin( ifile, ios::binary );
load_from_keyframe_file( fin, kfstruct );
fin.close();
cout << kfstruct.num_keyframes << endl;
return 0;
}
And here is the function I try to use for parsing the binary file and filling in the information in the struct kfstruct:
struct keyframe_struct
{
int num_views;
int num_keyframes;
vector<keyframe> keyframes;
};
int load_from_keyframe_file( ifstream &fin, keyframe_struct &kfstruct )
{
char keyword[100];
while ( !fin.eof() )
{
fin.getline( keyword, 100, 0 );
if ( strcmp( keyword, "views" ) == 0 )
{
fin.read(( char* ) kfstruct.num_views, sizeof( int ) );
}
else if ( strcmp( keyword, "keyframes" ) == 0 )
{
fin.read(( char* ) kfstruct.num_keyframes, sizeof( int ) );
}
}
}
Can you tell me what am I doing wrong? I'm sure I am making some huge errors here as I am just a beginner and I still don't understand clearly what should I and what should I not do with pointers.

You forgot to take the address of your fields:
fin.read(( char* ) &kfstruct.num_views, sizeof( int ) );
^^^
[As an aside, note that it's better from a maintenance point of view to do sizeof(kfstruct.num_views). So if the type ever changes, your code will still work.]

Instead of
( char* ) kfstruct.num_views
use
( char* ) (&kfstruct.num_views)
similarly in the other place.
otherwise you are writing to a location whose address is equal to the VALUE of your int. You don't want that. You want the address converted to char*. You take the address by '&' operator.

Related

Reading and Writing with char* to file

I am wondering how can you write data of type char*,int,double using char* and also reading a whole file line by line using again char* ? I know it can be done with std:string really beautiful but I am interested with char*. I have created a Write() method which writes char* successfully
but I don't know how to adjust it for ints and doubles, also I have started creating Read() method to read each line and save it to char* then print it to the console but I don't know how to implement it.
#include <iostream>
#include <fstream>
#include <cstring>
using namespace std;
void Write(char* fileName, char* pData)
{
ofstream file (fileName, ios::out | ios::app );
if (file.is_open())
{
size_t len = strlen(pData);
file.write(pData, len);
}
}
void Read(char* fileName, char* pData)
{
ifstream file(fileName, ios::in );
if(file.is_open())
{
file.read((char*)&pData, sizeof(pData));
file.close();
}
}
int main()
{
char* fileName = "E:\\cpp\\CarsIO\\data.txt";
char* data = "hello\n";
Write(fileName , data);
char* read = "";
Read(fileName , read);
return 0;
}
I have created a Write() method which writes char* successfully but I don't know how to adjust it for ints and doubles [...]
First of all, the function std::ofstream::write is intended for unformatted (binary) I/O. Since you are outputting text, it would be easier to use the formatted I/O functions, for example operator <<, like this:
void Write( char* fileName, char* pData )
{
ofstream file( fileName, ios::out | ios::app );
if ( file.is_open() )
{
file << pData;
}
if ( !file )
{
//TODO: handle error
}
}
In order for the function Write to print data of type int, you can simply create an appropriate overloaded function Write, like this:
void Write( char* fileName, int data )
{
ofstream file( fileName, ios::out | ios::app );
if ( file.is_open() )
{
file << data;
}
if ( !file )
{
//TODO: handle error
}
}
In order to make it also print the data type double, you can create an additional overloaded function for this data type. You can simply copy the function above and change int data to double data.
However, now you have 3 overloaded functions, one for the data type char *, one for int and one for double. That is a lot of unnecessary code duplication. It would be less messy to make a single template function, which can handle all three data types:
template <typename T>
void Write( char* fileName, T data )
{
ofstream file( fileName, ios::out | ios::app );
if ( file.is_open() )
{
file << data;
}
if ( !file )
{
//TODO: handle error
}
}
[...] also I have started creating Read() method to read each line and save it to char* then print it to the console but I don't know how to implement it.
The function std::ifstream::read is intended for unformatted (binary) input, not for formatted text input. Since you insist on using char* instead of std::string, I recommend that you use the function std::istream::getline in order to read exactly one line of text input, like this:
void Read( char* fileName, char* pData, std::streamsize count )
{
ifstream file(fileName, ios::in );
if(file.is_open())
{
file.getline( pData, count );
}
if ( !file )
{
//TODO: handle error
}
}
In your code, you were simply using sizeof(pData) to pass the size of the memory buffer. This will not work, because this will give you the size of the pointer pData (which is probably 4 or 8 bytes), instead of the size of the memory buffer. That is why the function Read must take an additional parameter which specifies the size of the memory buffer.
In your code, you are calling the function Read like this:
char* read = "";
Read(fileName , read);
This code is wrong, for two reasons:
You must ensure that the memory buffer is large enough to store the read data. This would be handled automatically when using std::string, but you must handle this yourself when using char*.
The line char* read = ""; makes the pointer read point to an (empty) string literal. String literals are read only. That is why C++ requires that pointers to string literals are declared as const char * instead of char *. You cannot pass a pointer to a read-only string literal to the function Read, because that function will attempt to write to that string literal, which causes undefined behavior.
In order to call the function Read and print the result, you can use the following code:
char buffer[100];
Read( fileName, buffer, sizeof buffer );
std::cout << buffer << "\n";

Data loss issue while converting the string from std::string to const char *

In this function, passing string as an argument (which is having the huge amount of data as a string)...
SendBytes method is defined like this
bool NetOutputBuffer_c::SendBytes ( const void * pBuf, int iLen )
{
BYTE * pMy = (BYTE*)pBuf;
while ( iLen>0 && !m_bError )
{
int iLeft = m_iBufferSize - ( m_pBufferPtr-m_pBuffer );
if ( iLen<=iLeft )
{
printf("iLen is %d\n",iLen);
memcpy ( m_pBufferPtr, pMy, iLen );
m_pBufferPtr += iLen;
break;
}
ResizeIf ( iLen );
}
return !m_bError;
}
bool NetOutputBuffer_c::SendCompressedString ( std::string sStr )
{
std::cout << sStr << endl;
const char *cStr = sStr.c_str();
std::cout << cStr <<endl;
int iLen = cStr ? strlen(cStr) : 0;
SendInt ( iLen );
return SendBytes ( cStr, iLen );
}
Tried printing the value of sStr to check whether it has proper data or not.
Then converting the std::string to const char *
After converting it,tried printing the value of cStr. But it(cStr) actually contains 5% of the actual data(sStr)...
what shall I need to do in order to get the whole data?
Can someone guide me in this regard?
strlen works for c-strings. That is, null terminated strings. It searches for a '\0' character, and returns that character's position as the length of the string. You have compressed binary data, not a c-string. It almost certainly contains a 0 before the end of the string. Use sStr.size() instead.
SendInt ( sStr.size() );
return SendBytes ( sStr.c_str(), sStr.size() );

Cannot convert std::string to in initialization

I am having an odd problem:
metadata.h:
class metadata
{
public:
metadata ( std::string filename );
template <class T> T get ( std::string key ) { return m_data.get<T> ( key ); }
private:
boost::property_tree::ptree m_data;
};
metadata.cpp:
metadata::metadata ( std::string filename )
{
try {
boost::property_tree::read_info ( filename, m_data );
} catch ( boost::property_tree::file_parser_error err ) {
std::cerr << "[!] Unable to open "
<< filename
<< ": "
<< err.message()
<< "!"
<< std::endl;
throw except ( "Error opening metadata file." );
}
}
asset.h:
template <class T> class assetManager {
public:
void load ( std::string filename );
T get ( std::string filename );
T operator[] ( std::string filename );
void drop ( std::string filename ) { m_assets.erase ( filename ); }
void clear() { m_assets.clear(); }
private:
std::map<std::string,T> m_assets;
};
template <class T> void assetManager<T>::load ( std::string filename )
{
if ( m_assets [ filename ] == nullptr ) {
m_assets.erase ( filename );
m_assets.insert ( std::pair <std::string,T> ( filename, new T ( filename ) ) );
}
}
template <class T> T assetManager<T>::get ( std::string filename )
{
T ret = m_assets.at ( filename );
return ret;
}
For some reason, this line here:
m_metadata.load ( boost::filesystem::current_path().string() + "/engine.conf" );
I am getting the following compiler error from g++:
src/core/../core/../asset/asset.h|22|error: cannot convert ‘std::string {aka std::basic_string}’ to ‘metadata*’ in initialization
At no point, as far as I can tell, have I programmed anything even slightly resembling a conversion of a string into my metdata pointer here. Any ideas?
Inside assetManager<T>::load this subexpression
std::pair <std::string,T> ( filename, new T ( filename ) )
is obviously incorrect. new T will produce T *. Meanwhile your pair is declared with T as its second member. You cannot use T * to initialize T, unless you have a proper conversion constructor in T.
If T is metadata, then for this problem I'd actually expect an error message saying that it is impossible to convert metadata * to std::string, not the other way around. You will see the same error if you attempt this
metadata m(new metadata("test"));
Another issue is that you are creating a pair in which the first member has type std::string. However, in std::map<std::string, T>::value_type the first member of the pair is actually const std::string. The code will compile, but it will require a conversion from your std::pair<std::string, T> to map's std::pair<const std::string, T>. This might become performance issue. Which is why a much better idea would be to use std::map<std::string, T>::value_type as pair type, instead of trying to spell it out manually.
In the comments you stated that m_metadata is assetManager<metadata*>. In that case T is metadata *. That explains everything, including the error message. In that case the problem is even more simple and localized. This
new T ( filename )
alone is what causing the error. That means that new T produces a metadata * object and attempts to initialize it with std::string. Hence the error message. It is not possible to initialize metadata * with std::string because it is not possible to convert std::string to metadata *.
On top of that, such new expression will return a metadata ** value, which is definitely not what you need.
I changed m_metadata to an assetManager then made a few changes thus:
template <class T> class assetManager {
public:
void load ( std::string filename );
T get ( std::string filename );
T operator[] ( std::string filename );
void drop ( std::string filename ) { m_assets.erase ( filename ); }
void clear() { m_assets.clear(); }
private:
std::map<std::string,T*> m_assets;
};
template <class T> void assetManager<T>::load ( std::string filename )
{
if ( m_assets [ filename ] == nullptr ) {
m_assets.erase ( filename );
m_assets.insert ( std::pair <std::string,T*> ( filename, new T ( filename ) ) );
}
}
template <class T> T assetManager<T>::get ( std::string filename )
{
T ret = *m_assets.at ( filename );
return ret;
}
It compiles and runs now. Though my problem is fixed it is still a mystery to me why the compiler thinks I was trying a std::string to metadata* conversion.

Reading lines from a binary file C++

I am trying to read a binary file and store it into a database, however I get a segmentation fault when I try to store a string type into the database. To be exact, the error occurs inside the push function:
new_node->name = name;
I can't seem to find a good solution on the web, and I'm aimlessly trying different things... any help would be appreciated.
//
// loadbin.cpp
//
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
using namespace std;
#include "studentsDB.h"
int main( int argc, char* argv[] ) {
string name;
string id;
int numCourses;
int crn;
vector<int> crns;
studentsDB sDB;
studentsDB::node *students = 0;
int in = 1;
if( argc > 1 ) {
ifstream infile(argv[in], ios::binary );
while( !infile.eof() ) {
infile.read( ( char* )(name.c_str()), sizeof( string ) );
infile.read( ( char* )(id.c_str()), sizeof( string ) );
infile.read( ( char* ) &numCourses, sizeof( int ) );
do{
crns.push_back( crn );
}
while( infile.read( ( char* ) &crn, sizeof( int ) ) );
sDB.push( &students, (string)name, (string)id, numCourses, crns );
}
//sDB.printList( students );
}
else
cout << "Not enough argument" << endl;
}
void studentsDB::push( struct node** head_ref, string name, string id,
int numCourses, vector<int>crns ) {
struct node* new_node = ( struct node* ) malloc(sizeof(struct node));
new_node->name = name;
//new_node->id = id;
new_node->numCourses = numCourses;
//new_node->crns = crns;
new_node->next = (*head_ref);
(*head_ref) = new_node;
size++;
}
This code is bad:
infile.read( ( char* )(name.c_str()), sizeof( string ) );
You can't write to the buffer returned by c_str(), it is not guaranteed to be long enough to hold your result. sizeof(string) has nothing to do with the size the string can hold, by the way. You will need to allocate your own char[] buffer to hold the results of infile.read, then convert to a string afterwards.

C++ Load text file, optimization [closed]

Closed. This question is off-topic. It is not currently accepting answers.
Want to improve this question? Update the question so it's on-topic for Stack Overflow.
Closed 11 years ago.
Improve this question
There is my source code loading text file and delimitting each line to single items (words).
How to further optimize the code? Testing empty lines (and other constructions) are (in my opinion) a little bit inefficient....
typedef std::vector < std::string > TLines;
typedef std::vector < std::vector < std::string > > TItems;
TItems TFloadFile ( const char * file_name )
{
//Load projection from file
unsigned int lines = 0;
char buffer[BUFF];
FILE * file;
TItems file_words;
TLines file_lines;
file = fopen ( file_name, "r" );
if ( file != NULL )
{
for ( ; fgets ( buffer, BUFF, file ); )
{
//Remove empty lines
bool empty_line = true;
for ( unsigned i = 0; i < strlen ( buffer ); i++ )
{
if ( !isspace ( ( unsigned char ) buffer[i] ) )
{
empty_line = false;
break;
}
}
if ( !empty_line )
{
file_lines.push_back ( buffer );
lines++;
}
}
file_words.resize ( lines + 1 );
for ( unsigned int i = 0; i < lines; i++ )
{
char * word = strtok ( const_cast<char *> ( file_lines[i].c_str() ), " \t,;\r\n" );
for ( int j = 0; word; j++, word = strtok ( 0, " \t;\r\n" ) )
{
file_words[i].push_back ( word );
}
}
fclose ( file );
}
return file_words;
}
Thanks for your help...
The line for ( unsigned i = 0; i < strlen ( buffer ); i++ ) is quite inefficient as you're calculating the length of buffer each time through the loop. However, it's possible that this will be optimised away by the compiler.
You're pushing items onto your std::vectors without reserve()ing any space. For large file, this will involve a lot of overhead as the content of the vectors will need to be copied in order to resize them. I just read #Notinlist's answer, which already talks about the inefficiencies of std::vector::resize().
Instead of reading each line into a vector through repeated fgets() calls, could you not simply determine the number of bytes in the file, dynamically allocate a char array to hold them, and then dump the bytes into it? Then, you could parse the words and store them in file_words. This would be more efficient than the method you're currently using.
Before optimizing, can you explain how big the file is, how long the code currently takes to execute and why you think it isn't already IO bound (ie due to hard disk speed). How long do you think it should take? Some idea of the type of data in the file would be good too (such as average line length, average proportion of empty lines etc).
That said, combine the remove-empty-line loop with the word-tokenising loop. Then you can remove TLines altogether and avoid the std::string constructions and vector push-back. I haven't checked this code works, but it should be close enough to give you the idea. It also includes a more efficient empty line spotter:
if ( file != NULL )
{
for ( ; fgets ( buffer, BUFF, file ); )
{
bool is_empty = true;
for (char *c = buffer; *c != '\0'; c++)
{
if (!isspace(c))
{
is_empty = false;
break;
}
}
if (is_empty)
continue;
file_words.resize ( lines + 1 );
char * word = strtok ( buffer, " \t,;\r\n" );
for ( int j = 0; word; j++, word = strtok ( 0, " \t;\r\n" ) )
{
file_words[i].push_back ( word );
}
lines++;
}
fclose ( file );
}
For one
file_lines.push_back ( buffer );
That is a very expensive line. If you don't have to use vector, then use a list instead. Maybe convert your list to a vector after you finished with the job.
If you absolutely in need of using vector for this purpose, then you should use some exponential increment instead, like:
if(file_lines.size()<=lines){
file_lines.resize((int)(lines * 1.3 + 1));
}
That way you will have much less cpu intensive .resize() operations, for a cost of minimal memory consumption overhead.
Simplified and converted to use std::list instead of std::vector.
typedef std::list< std::list< std::string > > TItems;
TItems TFloadFile ( const char * file_name )
{
using namespace std;
//Load projection from file
ifstream file(file_name);
TItems file_words;
string line;
for(getline(file,line); !file.fail() && !file.eof(); getline(file,line))
{
file_words.push_back(list<string>());
list<string> &words(file_words.back());
char *word = strtok((char*)line.c_str(), " \t,;\r\n" );
for(; word; word=strtok( 0, " \t;\r\n" ))
{
words.push_back( word );
}
if(!words.size())
file_words.pop_back();
}
return file_words;
}