Reading File in to a pointer

Started by
13 comments, last by rip-off 9 years, 3 months ago

Hello. I read some tutorials and books about pointers and I start to work on a project where I need to save some data to a file and load it again when I need it.

The problem is that I dont understand some basic notions like for example:

First of all I declare a User object that contains two pointers that are not pointing to any valid data. I initialize the pointer with some text and after that I write the data that is pointed to the file.
When I want to read the file again I pass another pointer that is not pointing to anything but grabs the data so I guess that the read() function creates the data and pass the adress in my pointer;
user14->username is a char pointer and I get valid data from the file.
But also usernameX is a char pointer and If I pass it in the second example I get errors.
Whats actually the difference between user14->username and usernameX ? Shouldnt both work the same.
What read() function actually do ? Why it works with uninitialized data and just with a simple pointer that has no data buffer assigned ?

 
typedef struct _user {
   char* username;
   char* password;
}user;
 
test_usernames_c.open("users_c.dta", ios::binary | ios::out);
 
user default_user;
default_user.username = "user";
default_user.password = "1234";
 
test_usernames_c.write((const char*)&default_user.username, sizeof(char) * (strlen(default_user.username) + 1));
test_usernames_c.write((const char*)&default_user.password, sizeof(char) * (strlen(default_user.password) + 1));
 
 
test_usernames_c.close();
 
 
 
// Read the file
test_usernames_c.open("users_c.dta", ios::binary | ios::in);
 
 

//A new user struct
user *user14;
user14 = new user;
 
 
 

test_usernames_c.read((char*)&user14->username, sizeof(char) * 6);
test_usernames_c.read((char*)&user14->password, sizeof(char) * 5);
//Here I get the correct data "user" and "1234" but my pointers from user14 (username and password) dont have any buffer atached.

char *usernameX;
test_usernames_c.read((char*)&usernameX sizeof(char) * 6);
//Doesnt work and if I initiate the usernameX pointer to point to some char buffer with size of 6 I get random characters.
Advertisement

Unless I am missing something, from what I see, "username" and "password" are declared inside of a struct and you are attempting to use them as global variables ( pointers ) .

Edit: All variables should be initialized when they are created.

I cannot remember the books I've read any more than the meals I have eaten; even so, they have made me.

~ Ralph Waldo Emerson

default_user.username is a character pointer, so taking it's address is creating a pointer to a point. You're writing the data at that address, and whatever happens to be after it, to the file. There is no inherent meaning to this data.

There are a few common ways of storing strings:

  • an arbitrary length block of text terminated with a sentinal character, e.g. a NUL or zero byte
  • writing the length of the string (e.g. as a 4 byte value of given endianness) followed by that number of characters
  • a fixed block of data terminated by a sentinal character
  • a length value followed by a fixed block of data
  • I'm going to assume we'll use the first mechanism. Writing is fairly easy, just dump the data pointed to by the string, including the NUL terminator.

    void writeString(std::ostream &out, const char *string)
    {
        int count = std::strlen(string) + 1;
        out.write(string, sizeof(char) * count);
    }
     
    // ...
     
    writeString(test_usernames_c, default_user.username);
    writeString(test_usernames_c, default_user.password);
    

    Now, the complicated part is reading, as we can't predict in advance how many characters we will need. The simplest solution is to read character by character:

    std::string readString(std::istream &in)
    {
        std::string result;
        while (true)
        {
            in.read(&c, sizeof(char));
            if ( c == '\0' )
            {
                return result;
            }
            else
            {
                result += c;
            }
        } 
    }
     
    std::string username = readString(test_usernames_c);
    

    Note: you'd probably be better off using std::string in your User struct. Manually juggling character pointers is not recommended, it opens you up to a whole slew of potential bugs, whereas std::string is much harder to break.

    Also, the code here ignores I/O errors, you should add such checking too.

    Ok, this is bad, mmmkay?

    user default_user;
    default_user.username = "user";
    default_user.password = "1234";

    You probably aren't doing what you think you are doing here. Basically you a priming a live hand grenade with this code.

    A pointer points at a location in memory, that's why they get their name. However, when you declare a pointer, that memory ISNT allocated.

    Going in to a big thing about how pointers work is a bit beyond what a forum like this is for ( for more details on C++ memory allocation, try this guide. ), I would highly recommend you start learning from a different source. Also, is there a reason why you are writing very old school C code here? I think my biggest suggestion is, pick a different learning source.

    EDIT: Ugh, just noticed this line...

    user14 = new user;

    So... this is C++ code after all and not actually C. In that case, there are many many many problems with it, and more likely, from whatever source you are learning from being horrifically out of date. For example:

    • struct typedef
    • direct memory usage in the first place
    • old school IO code
    • not using std::string

    Well I know that I need to initialize the data thats why I asked why it works. Seems if I declare the struct without typedef it works. I thought writing user.username = "blala" it intitialize the pointer with a buffer that contains "blabla" so I dont need to make a new to initiate data for that pointer. Also I though write() function from files initiates data for me so the pointer will point a valid buffer.

    It "works" because you assigned your pointers a value, so they point to something, even if that something is completely random.

    consider....

    char* myExplosiveVariable = "Idontreallyexist";

    std::cout << myExplosiveVariable[0];

    This code will run, but isn't doing what you think it is.

    The value "Idontreallyexist" isn't being assigned to myExplosiveVariable, it's location is. For now, that location kinda makes sense, but once you've gone out of scope then it no longer will. Now here's the thing, even though a variable is out of scope, that doesn't mean the contents get wiped, so a pointer to a variable that has been deallocated will continue to work... until that memory is reused, at which point... BOOM.

    Well I know that I need to initialize the data thats why I asked why it works. Seems if I declare the struct without typedef it works. I thought writing user.username = "blala" it intitialize the pointer with a buffer that contains "blabla" so I dont need to make a new to initiate data for that pointer. Also I though write() function from files initiates data for me so the pointer will point a valid buffer.

    Nope, this is where you went wrong.

    It's important again to remember, pointers only point to stuff. So what you are actually doing is creating a local temporary variable with the value "blala" and pointing at it's location. The problem is, if that pointer outlives that location in memory, you run into a world of hurt.

    This is where new comes in. Consider the line

    char * myStuff = new char(100);

    This is actually doing TWO things. A buffer in memory is being allocated that stores 100 chars, that's the new char(100) part. THEN a char * pointer is being created that points at that buffer. The initialization part is the new call.

    Yes I understand but I didnt got out of scope and managed to write and read the value when I use the user structure. Again when I use just char pointers it works. But never works if I write from user->username and read to char *usernameX and viceversa. Seems like when Im using the pointer from the struct I cant read the data to a simple char pointer. That is what I dont understand. Whats the difference between user->username (declared char* in the struct) and a simple char *usernameX; Still when I read the data written by user->username to a simple char* usernameX I get the correct result but I get corupted Stack.

    It's because you rely on undefined behaviour. Undefined behaviour is what occurs when you write some code which violates some of the rules in the C++ standard. The C++ compiler chooses to "ignore" these cases, because to find them generally involves adding run-time checks, which is counter to the C++ design philosophy. Instead, you, the programmer, are expected to be perfectly familiar with the C++ standard and not write code that falls into "undefined behaviour".

    If the C++ compiler generates a machine executable from such a program, as the name implies there is no restriction on what that program can do. It can fail to start, it can crash, it can run but silently corrupt it's own state. It is technically possible that such a program could erase all the user's files, though for most programs that is spectacularly unlikely.

    So, you had a program that depended on undefined behaviour, and it "appeared" to work. But that isn't necessarily true, as if you compile it in another compiler, it may not "work". Or if you make a change elsewhere in the program, it may appear to break.

    This is why you're getting different behaviour when using a local variable and a pointer in a dynamically allocated structure.

    So I did write user->username to file and read from the file with a <pointer of a char pointer> (char **usernameX) that was initialised and it worked. Is that normal behavior ?

    Btw Im sorry for the stupid questions and I'm doing my best to undestand the language;

    This topic is closed to new replies.

    Advertisement