Search

18.7 — Random file I/O

The file pointer

Each file stream class contains a file pointer that is used to keep track of the current read/write position within the file. When something is read from or written to a file, the reading/writing happens at the file pointer’s current location. By default, when opening a file for reading or writing, the file pointer is set to the beginning of the file. However, if a file is opened in append mode, the file pointer is moved to the end of the file, so that writing does not overwrite any of the current contents of the file.

Random file access with seekg() and seekp()

So far, all of the file access we’ve done has been sequential -- that is, we’ve read or written the file contents in order. However, it is also possible to do random file access -- that is, skip around to various points in the file to read its contents. This can be useful when your file is full of records, and you wish to retrieve a specific record. Rather than reading all of the records until you get to the one you want, you can skip directly to the record you wish to retrieve.

Random file access is done by manipulating the file pointer using the seekg() function (for input) and seekp() function (for output). In case you are wondering, the g stands for “get” and the p for “put”.

The seekg() and seekp() functions take two parameters. The first parameter is an offset that determines how many bytes to move the file pointer. The second parameter is an Ios flag that specifies what the offset parameter should be offset from.

Ios seek flag Meaning
beg The offset is relative to the beginning of the file (default)
cur The offset is relative to the current location of the file pointer
end The offset is relative to the end of the file

A positive offset means move the file pointer towards the end of the file, whereas a negative offset means move the file pointer towards the beginning of the file.

Here are some examples:

Moving to the beginning or end of the file is easy:

Let’s do an example using seekg() and the input file we created in the last lesson. That input file looks like this:

This is line 1
This is line 2
This is line 3
This is line 4

Here is the example:

This produces the result:

is line 1
line 2
his is line 4

Note: Some compilers have buggy implementations of seekg() and tellg() when used in conjunction with text files (due to buffering). If your compiler is one of them (and you’ll know because your output will differ from the above), you can try opening the file in binary mode instead:

Two other useful functions are tellg() and tellp(), which return the absolute position of the file pointer. This can be used to determine the size of a file:

This prints:

64

which is how long sample.dat is in bytes (assuming a carriage return after the last line).

Reading and writing a file at the same time using fstream

The fstream class is capable of both reading and writing a file at the same time -- almost! The big caveat here is that it is not possible to switch between reading and writing arbitrarily. Once a read or write has taken place, the only way to switch between the two is to perform an operation that modifies the file position (e.g. a seek). If you don’t actually want to move the file pointer, you can always seek to the current position:

If you do not do this, any number of strange and bizarre things may occur.

(Note: Although it may seem that iofile.seekg(0, ios::cur) would also work, it appears some compilers may optimize this away.)

One other bit of trickiness: Unlike ifstream, where we could say while (inf) to determine if there was more to read, this will not work with fstream.

Let’s do a file I/O example using fstream. We’re going to write a program that opens a file, reads its contents, and changes any vowels it finds to a ‘#’ symbol.

Other useful file functions

To delete a file, simply use the remove() function.

Also, the is_open() function will return true if the stream is currently open, and false otherwise.

A warning about writing pointers to disk

While streaming variables to a file is quite easy, things become more complicated when you’re dealing with pointers. Remember that a pointer simply holds the address of the variable it is pointing to. Although it is possible to read and write addresses to disk, it is extremely dangerous to do so. This is because a variable’s address may differ from execution to execution. Consequently, although a variable may have lived at address 0x0012FF7C when you wrote that address to disk, it may not live there any more when you read that address back in!

For example, let’s say you had an integer named nValue that lived at address 0x0012FF7C. You assigned nValue the value 5. You also declared a pointer named *pnValue that points to nValue. pnValue holds nValue’s address of 0x0012FF7C. You want to save these for later, so you write the value 5 and the address 0x0012FF7C to disk.

A few weeks later, you run the program again and read these values back from disk. You read the value 5 into another variable named nValue, which lives at 0x0012FF78. You read the address 0x0012FF7C into a new pointer named *pnValue. Because pnValue now points to 0x0012FF7C when the nValue lives at 0x0012FF78, pnValue is no longer pointing to nValue, and trying to access pnValue will lead you into trouble.

Rule: Do not write addresses to files. The variables that were originally at those addresses may be at different addresses when you read their values back in from disk, and the addresses will be invalid.

A.1 -- Static and dynamic libraries
Index
18.6 -- Basic file I/O

58 comments to 18.7 — Random file I/O

  • Martin

    Hi Alex, thank you for this wonderful tutorial.
    I was wondering why you mentioned seekp() and tellp() a lot but never give an example what it would be used for. Maybe this is something to add? Just for the sake of completeness?

  • Sihoo

    Thank you so much Alex! This has been the greatest tutorial. I really appreciate all the work and effort you’ve put into this tutorial!

  • Lamont Peterson

    Alex,

    In the first example program, using seekg (), the three lines of output the lesson says to expect are:

    is line 1
    line 2
    his is line 4

    You then comment that if my output differs, it indicates that my compiler is faulty.  However, I don’t agree with that, since the math doesn’t add up.  The third line only shows 13 visible characters plus 1 newline character for 14 characters total, but the command was "seekg (15, std::iOS::end);", or in other words, 15 characters from the end of the file.  Since EOF is actually the next position after the last character (‘\n’ in this case), it is not part of the calculation of 15 characters before the "end" point.

    Here’s what I’m seeing:

    is line 1
    line 2
    This is line 4

    For my output, the math adds up, correctly.  This is 14 visible characters, plus 1 newline character, for 15 total characters read before the end of the file.

    Now, perhaps someone might think that the "getline (inf, strData);" moves the pointer one position (character in text mode) then reads in character "under" the pointer, moves it again, reads another character … and so on, until it reaches the point where the line ends (this is getline (), after all).  But, that would be foolish, because if it did that, we’d never be able to read the first character of something (a file, from the keyboard) with getline ().

    So, this starts to make me wonder if you have a small typo there, (just missing the "T" at the beginning of line three of the output)?  But, since you were particular about "buggy" compilers when using seekg () and tellg () in text mode and suggest using binary mode (which produces the same results for me), I start to think there’s something else you might know what I haven’t "found" to include in the thinking here.

    But, I also now reach the end of the lessons.  Thank you and I’m looking forward to seeing you publish more lessons in the future.

  • Gapo

    I fail to understand how does the while(obj.get(chChar)) iterate threw each and every character in our file ?

    • Lamont Peterson

      Gapo,

      Because the "obj.get (ch)" call moves the pointer forward 1 position after reading 1 character into ch.  Thus, as the while () loop runs, it runs through the file.

  • Clay

    Thank you so much Alex! I am currently in a C++ class and the teacher is horrible. You have helped me so much! Also could you ever do something involving the teachings of electronics?

    • Alex

      I’m glad you’ve found these tutorials useful. I don’t see ever doing a series on electronics or electrical engineering because I don’t have enough knowledge of those subjects to teach them effectively.

  • Chris

    Hello Alex.

    These tutorials have taught me a lot about C++ and coding in general, and I can’t thank you enough for putting so much time and effort into writing these.

    Since I’m almost done the entire tutorial, do you know where I could go to continue learning about c++, and maybe create more than just console applications?

    Thanks.

  • KnowMore

    Alex, But how to read/write pointers from/to disk ?
    Extraction operator(>>) doesn’t work for reading ptrs from disk !

    And, disk here refers to file only, no?
    Waiting for your reply….
    Thanks in Advance 🙂

    • Alex

      You can’t write pointers to disk, and it doesn’t make sense to do so. When the program goes to load the data from disk, everything may be at a different address (especially if the program has been run again), and none of the pointer data will be valid any more!

      This presents a challenge -- we need some way to convert objects with references into a “flat” format that can be stored, transmitted, and reconstructed later. This process is called “serialization”, and it’s a non-trivial topic, as C++ doesn’t come with any built-in serialization capabilities. That means it’s up to the programmer to provide serialization capabilities for classes.

      See https://en.wikipedia.org/wiki/Serialization for more information. This topic probably deserves its own set of lessons, as it can be quite complicated.

  • Hardik

    Alex? Won’t the eofbit be set in this code ?

    Here, the file pointer is at the EOF.

    • Alex

      I believe so. Why do you ask?

      • Hardik

        Because when i run the above code with the following  code :-

        It doesnt print this EOFBOT statement 🙁 Is it due to the compiler or something else? Should I Open The File In Binary Mode Instead ?

        • Alex

          Looks like I was incorrect. See https://stackoverflow.com/questions/35628288/seekg-does-not-set-eofbit-when-reaching-eof-of-a-stream-is-it-by-design/35628419

          • Lamont Peterson

            Alex,

            Have you tried out the code examples in this Stack Overflow post?

            I put it together, and using stringstream for"input", it works fine, but using ifsteram, it’s like booleans no longer work for .good () or .eof () (I didn’t play with bad () or fail (), yet).  So, when using ifstream, the program just merrily spits out data past the end of the file (it isn’t reading anything, char == -1) showing that eof () returns true and good () returns false, but the while looks still hums along.  I tried some explicit if statements testing against boolean and integer values.  I’ve loaded up on Llamas and they seem to be returning boolean values, but while and if can’t seem detect false from them.

            Seeking to the end of the file and recording the "fileLength" for later comparison works fine, but I’d like to be able to rely on good (), bad () and eof () results, just to keep things a little more simple and obvious in code.

            I’m sure I’m going to feel really dumb when someone figures out what I’ve done wrong when using ifstream here:

            Comment out the "if (input.tellg () >= fileLength)" block to see it just keep going (be ready to hit <CTRL>+<C>).

  • apfelpektin

    typo in the first little snippet of..
    "Reading and writing a file at the same time using fstream"

    also this restoring of the read/write position might not work with some compilers. with the g++ 4.8.4 i’m using currently tellg() and tellp() will always return the same value after any seekg, seekp, write or read. on the other hand someone on the net wrote they work separately with his g++ 4.7.2.

    this is typical:
    - first some reference website didn’t tell me what the g/p postfixes actually mean, which makes remembering a command harder.
    - then i learned it means get (read) and put (write) positions and i thought "yes, what a cleverer thing to have those working independently.
    - now i not only learned 4 commands instead of only 2 (seek,tell) but additionally i learned this abstraction has basically no relevance for reality and you shouldn’t rely on it, funny c++.

  • Rohit

    Hi Alex!
    What’s the use of -1 here?
    iofile.seekg(-1, ios::cur); // why do we want the pointer to move one byte backwards to our current position?

    • Alex

      The program is trying to replace vowels with #. So first we have to read the character to see if it’s a vowel. If so, then we need to replace it. Replacing it means backing up 1 character, then overwriting the vowel with a ‘#’ symbol. The line you quoted backs up 1 character.

      • Rohit

        thats what i am asking, is our pointer is at a vowel then why do we need to move to the previous character to replace the vowel? May be I am missing somewhere please clear it.

        Thanks.

        • apfelpektin

          after you have read a character to check if it is a vovel, the file pointer will already point to the next character, ready for the next read operation so to speak.
          think of it this way:
          if you type a character in an editor the caret will then be placed after the character you just typed (analogous to the char read from a file). if you want to overwrite that char again (without using backspace) you would move the caret 1 position to the left so it is positioned before the char, then activate insert mode in your editor and type the new character like ‘#’ for example.

  • The Long

    Hi, Alex.
    In the vowel example, at line 45, when I use your code

    it works perfectly fine. But when I try the other one

    it does not. I figure out that the program just insert ‘#’ but does not overwrite the content at the pointer position. I still don’t know why because to me, these two statement produce the same result: put the file pointer at the current position so that we can continue read the file. Thanks in advance.

    • Alex

      I suspect some compilers see the request to seekg 0 bytes from the current position and just ignore it, causing the program not to switch modes.

  • Mauricio Mirabetti

    Alex, perhaps a typo, after you changed the stream name from inf to iofile on that specific example:

    "Although it may seem that inf.seekg(0, ios::cur) would also work" -> iofile.seekg(0, ios::cur)

    Best regards.

    Mauricio Mirabetti

  • zhaojie niu

    thank you very much!

  • zhaojie niu

    Hi, Alex, I want to finish learning this tutorial in one month and I finished 6 chapters, would you like to tell me which part is more important and which part I should  spend more time to learn?
    #1 how long could I finish learning this tutorial if I have finished 6 chapters?
    #2which chapters is more important to learn as a freshman in C++?

    • Alex

      1) I have no idea, it depends on how fast you read and internalize the information.
      2) The most important chapters are 1-8. But as a freshman, assuming you’re taking a standard course load, you’ll likely cover most of the information in this tutorial (plus other things as well).

  • Kausthub

    Hi Alex
    You haven’t taught how to delete a record in a file having classes or structures

    • Alex

      Generally, adding anything to or removing anything from a file directly is a bad idea.

      You’re better off reading the contents of the file into memory, modifying them in memory, and then writing them out.

  • LeoNardo

    hii
    I ‘m confusing about
    How to modify the content in file by random access

  • RWDavid

    Hi Alex 🙂
    Is there anyway to write into a file where it does not overwrite the existing characters? For example, you search for every vowel in the text file and you want to insert a ‘$’ after the vowel.

    I tried opening the file with the std::ios::app flag, but it only adds text at the end.

    • Alex

      The best way to do this is either to:
      1) Read the contents of the file into memory, modify the content in memory, and write them back out, or
      2) While you’re reading the contents of the file, write them out into a new file and make any additional changes at that point.

      Trying to insert bytes into the middle of files is a recipe for disaster.

  • Sameeha Basha

    Hi,
    Why is it necessary to do a seek operation in order to switch between read and write?

    • Alex

      I don’t actually know. It must have something to do with the way the streams are implemented, but I don’t have enough in depth knowledge on that topic to say.

  • Hannah

    Hi,

    is it possible to delete the contents of a file between locations A and B?

    I’m assuming that would be more difficult than overwriting, because we’re changing the location of the remaining content?

    • Alex

      It’s generally a bad idea to try to add/delete content from the middle of a file. For this kind of thing, it’s easier to either:
      1) Read the whole file into memory, skipping the content you want to delete, then write the contents of memory back out to disk.
      2) Open a new file and copy all the parts of the source file you want to keep into the destination file. Then delete the source file and rename the destination file to the source file.

  • Kodnot

    First of all, a few minor typos:
    1. Before the first big example: should be "Here’s" instead of "Heres";
    2. In "Reading and writing a file at the same time using fstream": "…and changes the any vowels it finds to a ‘#’ symbol" - no need for "the".

    Also, this code does not work for me unless the file is opened in binary mode. There is this quote: "The only seeking you can do in a text file is to the start of the file, to the current position, or to a streampos returned from a call to tell[gp]()." - random coder on bytes.com, and as far as I looked into it, seekg and seekp in other situations whilest dealing with such text files is undefined or unreliable. Maybe you should adress that in the tutorial^^

    • Alex

      Typos fixed. It looks like some compilers have a buggy implementation of seekg() and tellg() when used in text mode. I’ve added a note about trying binary mode in this case.

  • Lokesh

    #typo
    "One other bit of trickiness: Unlike istream," -> ifstream

  • Aaron

    What do I do if I want to read/write a single line?
    Is there a way to seek by line, rather than by bytes?

    I want to write a database file that contains variable length entries, reads them into an array for sorting/printing etc, then writes them back to the file when I’m done.
    I figure that if every entry is on its own line, I can just read that entire line into each array element, and write them back the same way, but I don’t know how to do that.

    • Alex

      To read a single line, use getline().

      For what you want to do, I don’t think you need to seek by line. Just parse the entire database file upon load (using getline()), make your modifications to it in memory, and then write it back out when you’re done.

      As long as the file isn’t HUGE, this should work.

  • Matt

    Typo ("it’s" should be "its"):
    * "- that is, skip around to various points in the file to read it’s contents."
    * "We’re going to write a program that opens a file, reads it’s contents, and changes the any vowels it finds to a ‘#’ symbol."

  • loift

    I need to do many manipulations on a file on a bit level. Is there a better way to do this than: (1)open the file in input mode (2)open a second file in output mode (3)read the input file as a string (4)individually convert each character of this string to its binary (ascii/utf-8) value and append/write this to the output file (5)do manipulations on the output file (6) manually convert the output file back by reading the output file as a string 8 “boolean” characters at a time and turning it into an ascii/utf-8 value.

  • hi..I want to find whether a string is present or not in a file which i have already written and its contents are being displayed…can someone help me with the code…

  • Casey

    Is it possible to pass fstream objects as parameters? I am writing a simple function that calculates the size of a file stream passed to it. Here is the code.

    Any help is appreciated.

    Also, is there a tutorial on advanced operations with binary files? I’m writing a file archive application and I need a little help with the C++ filestream objects.
    I would like to weave files together byte by byte. I would also like to write variables, like the number and names of files in the archive, to the top of the file for easy reference.

    Thank you for these tutorials. They have been a great resource for me.

    • Casey

      I figured out my problem. The ios namespace lives inside std, so to use ios, I must also use std. My fstream parameters were being declared outside of their scope.

      Man, this stuff gets confusing!

  • Hello
    why this code is true?

  • Hello
    why this code is true?
    [code]

    #include
    #include
    using namespace std;

    int main()
    {
    int b[4]={1,1,5,1};
    int c[4]={0};
    fstream A(“File.txt”,ios::binary|ios::in|ios::out);

    if(!A)
    {
    return 1;
    }
    A.write((char *) (&b),sizeof(b));//(&b) must be (b) but ?????!!!!!
    A.seekg(0);
    A.read((char *) (c),sizeof(c));
    cout<<c[2];
    return 0;
    }

  • dekaya

    I modified your code slightly to get it to work on my win2k machine.
    using dev-cpp

    I added :

  • Sam

    Hi, I tried your vowel replacement program. It works for single-line text files, but if there’s more than 1 line, the program seems to break down. Even the first line isn’t “translated” properly.

    Input:
    This is line One.
    This is line Two.

    Output:
    This#is#lin# On#.
    Thi# i# li#e#Two#

    This only thing I changed was the .dat extension to .txt

    Seems like the ‘new line’ character screws up the either the get or put pointer, although this doesn’t explain why even the first line isn’t working properly.

    • Hi Sam/Alex,

      My results were not quite the same as Sam’s but the code did not work.

      The problem I believe lies in the seekg(0, ios::cur) call after we output the ‘#’. As far as I can make out this is not good enough to convince the io system that something has happened (internally it optimises away the call to fseek and I think that means its idea of where we are is not correct).

      Change that line for these two:

      iofile.seekg( -1, ios::cur );
      iofile.seekg( 1, ios::cur );

      That worked for me.

      What I can’t understand is why we don’t need to use seekp (instead of seekg) before the write…

      Grant

      • I think you can also use:

        iofile.seekg( iofile.tellg(), ios::beg);

        Grant

        • Hmmm, that is interesting about seekg(0, ios::cur) not working for you. It worked fine for me, but maybe it is being optimized away in your case as you suggest.

          I added a short blurb about tellg() and tellp() to the tutorial and also changed the example to use iofile.seekg(iofile.tellg(), ios::beg);, though I do have some concerns about the performance ramificaitons of doing such a thing (not sure if it’s smart enough to convert that into a relative position, or whether it’s going back to the beginning of the file each time and then counting it out).

          As for the seekg()/seekp() difference, as far as I can tell with fstream they appear to be identical.

Leave a Comment

Put C++ code inside [code][/code] tags to use the syntax highlighter