Home > OS >  How to read and write text and numbers in the same file?
How to read and write text and numbers in the same file?

Time:11-21

I've got a problem which is REALLY annoying and incredibly puzzling, because it doesn't seem possible at all, and yet it's happening!

All I'm trying to do is write and read a file consisting of one string followed by some unsigned longs (stored as the numbers, but NOT converted to the text that would display them).

I'm a bit out of practice with C/C (but I'm getting back into it heavily now), so I was just trying to use what I'm used to for file management, which is fprintf, fscanf, fread and fwrite. You may have better ways in mind, and you can suggest them if you want, but I already have code written with the other functions and I probably won't rewrite it all. I really just want to get my existing functions to work, as stupid as they may be. You probably use the << and >> operators or something, but I never got into the habit of doing that, because for my personal aesthetic taste oporators just seem stupid for file access procedures, and I'd rather just use functions. And the ones that I mentioned always seemed to work fine for me so I've stuck with them. In any case, I have two main priorities:

  • get the thing to work

  • figure out why it's giving me such weird results in the first place, so that I can prevent it from ever happening again!

So first I tried to just print the text string using fprintf and the longs using fwrite, and then read the string using fscanf and the longs using fread. But the fscanf didn't seem to be working so I read something that said that it only reads until a space rather than a null character (WTF is that about?!), so then instead, I implimented a loop to read it a character at a time:

string text = "";
char* currentChar = new char(0);
while(true)
{
    fread(currentChar, sizeof(char), 1, file);
    if(*currentChar == 0)
    {
        break;
    }
    text  = *currentChar;
}

I realize that this is probably not the simplest or most efficient way to do it, but frankly I don't care, because it's just one little string, so it's not as though it will bog down the system. But the point is that it should at least WORK correctly, shouldn't it? Notice that I terminate the string with a null character, and by the time I've finished reading the string, I have already read the null character from the file, so that when I begin reading the longs next, they should begin just AFTER the null, correct?

But then I noticed that the longs that I was reading were not what I had written into the file already, so I decided to replace my fprintf with something over which I could have a lot more control, and I implemented a similar loop for writing:

unsigned int textLength = textString.length();
const char* text = textString.c_str();
for(unsigned int i = 0; i < textLength; i  )
{
    fwrite(&text[i], sizeof(char), 1, file);
}
const char* null = new char(0);
fwrite(&null, sizeof(char), 1, file);

But when I run this to save the data into the file, even though it's writing all of the text, and SEEMS to be writing the null character at the end, when I later read the file using the other function, it somehow picks up one extra character at the end of the text just before the null! And what the extra character equals depends on which file it is (the files are of the same format but with different data), though it always seems to be just one character (but to be fair, I've only tried it with a couple of text strings, but with numerous different longs that vary each time).

I suspect this may be somehow indirectly caused by the bytes of the longs being flipped because of the stupid big/little endian nonsense (why can't everyone agree on a standard?), but even if that's the case, I read and write the null character as a single byte BEFORE reading/writing ANY longs, so I don't see how the bytes could be transposed with the null character! Though it's only speculation that it could even have anything to do with byte transposition.

Actually, I just tested it again, and I've determined that each time, the extra character at the end varies along with different longs that appear after it, even when compared between two files with the same text string at the beginning, which seems to give more weight to my theory that it might be transposing bytes of the longs, but I still don't see how that's possible, given that I read/write the null character BEFORE I attempt to read/write ANY longs!

Also, either way that doesn't exaplain why when I was still using fprintf (before I replaced it with my write loop), even though it did NOT put an extra character at the end of the string, it somehow STILL read the wrong longs! Do the fread and fwrite functions don't match formats, so that one flips the bytes and the other doesn't? That would explain the weird results, but given that the two functions are in the same file, I would be amazed at the stupidity of anyone to write them to be incompatible with each other!

But in any case, I've been working on this problem all day and I was hoping to fix it today, but I don't think that's feasible anymore. How can this ridiculous problem possibly be happening and how can I fix it?! Thanks in advance!

CodePudding user response:

It changes based on whether you are using C or C what you were using is how you would do it in C but in C you would want to look in to std::cin and std::cout. Those are what you would want to use the data streams (<< or >>) for.

CodePudding user response:

We are here to help.

And,I am truly sorry that the answer comes so late. I had first to lookup my 30 years old C-books and try to reunderstand the C commands. So, I apologize again.

One of the biggest problems that you may face is that you store a string of arbitrary length.

But, when reading back the string, you do not know, how many byte it had. Therefore you must write the terminating 0 to the file and then, for reading back, you need to read byte by byte, up to and including the terminating 0.

Additionally. If you do not have modern C strings, then you need to do all the dynamic memory management by yourself. That means effort. But no problem.

It is easier, if you have a fixed size string. Then you can use the std::fwriteand std::fread to read the complete string in one rush.

For the moment, I do ignore that this will not work for very long data, because it maybe then necessary to read to file in chunks, if the buffered IO buffer is too small. Also this is no problem. If you face it, then please feedback and I will of course write some new code for you showing also how to do that.

I added also many comments to give you a better understanding.

Please see first the solution with full dynamic memory management.

#include <cstdio>
#include <cstring>

int main() {

    // 3 long test values. We will use this valuestobe ableto check problems wit endianess
    long value1a = 305419896;  // Which is equivalent to the hex number 0x12345678
    long value2a = 591751049;  // Which is equivalent to the hex number 0x23456789
    long value3a = 878082202;  // Which is equivalent to the hex number 0x3456789A

    // A test string with some arbitary length
    const char *testString = "abcdefghijklmnopqrstuvwxyz";

    // Part 1. Write data ------------------------------------------------------

    // Now, open a file and write the values. Open file in binary mode
#pragma warning(suppress : 4996)
    std::FILE* fptr = std::fopen("r:\\test.txt", "wb");

    // Check,if the file could be opened.
    if (fptr) {
        // Write values to file
        std::fwrite(testString, std::strlen(testString) 1, 1, fptr);
        std::fwrite(&value1a, sizeof(long), 1, fptr);
        std::fwrite(&value2a, sizeof(long), 1, fptr);
        std::fwrite(&value3a, sizeof(long), 1, fptr);

        // CLose the file and then try toreread data
        fclose(fptr);

        // Part 2. Read data ------------------------------------------------------
#pragma warning(suppress : 4996)
        fptr = std::fopen("r:\\test.txt", "r");

        // Check, if the file could be opened for reading
        if (fptr) {

            // Now read byte by byte and store result in dynamic memory. First create dynamic memory
            unsigned int reservedSpace = 2;
            char* myString = new char[reservedSpace];

            // This is theindex in our string
            unsigned int readIndex = 0;

            // We do not know the length of the string in the file, so, we will read until we find the terminating 0
            char c = ' ';
            while ((c != '\0') && (c != EOF)) {

                // Read character
                c = static_cast<char>(fgetc(fptr));

                // Before we can store the new character, we need to check, if we have enough space in our target string
                if (readIndex >= reservedSpace) {

                    // No, not enough space. Get more space, DOubele the previous size
                    reservedSpace = reservedSpace * 2;

                    // Get new space
                    char* temp = new char[reservedSpace];

                    // Copy old data to this space
                    for (unsigned k = 0; k < readIndex;   k)
                        temp[k] = myString[k];

                    // Release old memory
                    delete []myString;

                    // And assign new memory to old variable. So, now we do have a bigger string
                    myString = temp;
                }

                // Now store the just read character
                myString[readIndex] = c;

                // And set the new read index
                  readIndex;
            }
            // All read. Now set the string terminator
            myString[readIndex] = '\0';

            // Next. Try to read 3 longs and check result
            long value1b=0, value2b=0, value3b=0;
            unsigned int readValues = 0;
            readValues  = std::fread(&value1b, sizeof(long), 1, fptr);
            readValues  = std::fread(&value2b, sizeof(long), 1, fptr);
            readValues  = std::fread(&value3b, sizeof(long), 1, fptr);

            if (readValues == 3) {
                // Show resulting output
                std::printf("String:\t\t%s\nValue 1:\t%ld\nValue 2:\t%ld\nValue 3:\t%ld\n", myString, value1b, value2b, value3b);
                if (!strcmp(testString, myString) && value1a == value1b && value2a == value2b && value3a == value3b) {
                    std::printf("\nEverything read correctly\n\n");
                }
                else {
                    std::printf("\n*** Error. Data could obviously not be read back correctly\n\n");
                }
            }
            else {
                std::printf("\n*** Error could not read long values\n\n");
            }
            // Close the file
            fclose(fptr);

            // Release memory
            delete[] myString;
        }
        else {
            // File could not be opened for reading. Show error message.
            std::printf("\n*** Error: could not open file for reading\n\n");
        }
    }
    else {
        // File could not be opened for writing. Show error message.
        std::printf("\n*** Error: could not open file for writing\n\n");
    }
}

And now the function for fixed size strings.

#include <cstdio>
#include <cstring>

int main() {

    // 3 long test values. Wewill use this valuestobe ableto check problems wit endianess
    long value1a = 305419896;  // Which is equivalent to the hex number 0x12345678
    long value2a = 591751049;  // Which is equivalent to the hex number 0x23456789
    long value3a = 878082202;  // Which is equivalent to the hex number 0x3456789A

    const int StringSize = 50;

    // A test string with some arbitary length
    const char testString[StringSize] = "abcdefghijklmnopqrstuvwxyz";

    // Part 1. Write data ------------------------------------------------------

    // Now, open a file and write the values. Open file in binary mode
#pragma warning(suppress : 4996)
    std::FILE* fptr = std::fopen("r:\\test.txt", "wb");

    // Check,if the file could be opened.
    if (fptr) {
        // Write values to file
        std::fwrite(testString, StringSize, 1, fptr);
        std::fwrite(&value1a, sizeof(long), 1, fptr);
        std::fwrite(&value2a, sizeof(long), 1, fptr);
        std::fwrite(&value3a, sizeof(long), 1, fptr);


        // CLose the file and then try toreread data
        fclose(fptr);

        // Part 2. Read data ------------------------------------------------------
#pragma warning(suppress : 4996)
        fptr = std::fopen("r:\\test.txt", "r");

        // Check, if the file could be opened for reading
        if (fptr) {

            // Next. Try to read 3 longs and check result
            long value1b = 0, value2b = 0, value3b = 0;
            char myString[StringSize];

            unsigned int readValues = 0;
            readValues  = std::fread(myString, StringSize, 1, fptr);
            readValues  = std::fread(&value1b, sizeof(long), 1, fptr);
            readValues  = std::fread(&value2b, sizeof(long), 1, fptr);
            readValues  = std::fread(&value3b, sizeof(long), 1, fptr);

            if (readValues == 4) {
                // Show resulting output
                std::printf("String:\t\t%s\nValue 1:\t%ld\nValue 2:\t%ld\nValue 3:\t%ld\n", myString, value1b, value2b, value3b);
                if (!strcmp(testString, myString) && value1a == value1b && value2a == value2b && value3a == value3b) {
                    std::printf("\nEverything read correctly\n\n");
                }
                else {
                    std::printf("\n*** Error. Data could obviously not be read back correctly\n\n");
                }
            }
            else {
                std::printf("\n*** Error could not read long values\n\n");
            }
            // Close the file
            fclose(fptr);
        }
        else {
            // File could not be opened for reading. Show error message.
            std::printf("\n*** Error: could not open file for reading\n\n");
        }
    }
    else {
        // File could not be opened for writing. Show error message.
        std::printf("\n*** Error: could not open file for writing\n\n");
    }
}

Please note. No C programmer would do it in that way.

This is basically C-code

And my compiler gives a lot of warnings, because I use these C-Functions.

And, last but not least. There are many many different possible solutions.

I hope that I could help you at least a little. If you need more help, then please inform me via comment.

  • Related