0

In a situation where a text file is read line by line through a while loop, what is the recommended method for dealing with errors?

(note: fggets from here is used)

bool SomeClass::ReadFile(int ignorelines) {
    FILE *file = fopen(m_filepath.c_str(), "r");
    if (file!=NULL) {
        char *buffer;                           //line reading buffer
        while (fggets(&buffer,file)==0) {       //read line (fggets returns non-zero at provided terminator or \eof)
            if (ignorelines>0) {
                ignorelines--;                  //count the lines ignored
                free(buffer);
                continue;
            }
            line.assign(buffer);
            if (!PerformLineCheck1(buffer)) {   
                m_error+="The line is not allowed.\n";
                free(buffer);
                fclose(file);
                return false;
            }
            if (!PerformLineModification1(buffer)) {
                m_error+="The line could not be modified properly.\n";
                free(buffer);
                fclose(file);
                return false;
            }
            if (!PerformLineExtraction(buffer)) {   
                m_error+="The variables could not be extracted.\n";
                free(buffer);
                fclose(file);
                return false;
            }
            //extracted all, now continue with next line
            free(buffer);                       //clear up the line
        }
        fclose(file);
        return true;
    }
    m_error+="There was an error opening the data file.\n";
    return false;
}

There are some options I could think of:

  • use break; to get out of the while loop: but then I would need to add free(buffer); which can cause freeing memory twice.
  • use try and catch, however this deals with all other exceptions in those functions as well and suggested was to use assert (as suggested here). However, assert will assume code errors and break operation, while for this situation program execution can continue, the function simply needs to return false.

What is the suggestion for something like the loop above?

Community
  • 1
  • 1
DoubleYou
  • 1,057
  • 11
  • 25
  • 1
    You might first rewrite this in C++. Also your program has undefined behavior (check what you really do with `buffer`) – quantdev Aug 13 '14 at 07:42
  • I know I'm using C functions. And what's the undefined behavior? `fggets` automatically allocates `buffer`. I know I haven't provided all the class variables (such as `m_error`, which is a `std::string` actually). – DoubleYou Aug 13 '14 at 07:44
  • Well, you could use try/catch and only catch the exceptions you care about (or throw a custom exception). Or you could set buffer=nullptr every time after you have free'd it, and then break from the while loop on error (anytime), and if the buffer is not nullptr, free it. – fileoffset Aug 13 '14 at 07:46

3 Answers3

0

First, try and catch is not an entirely bad idea. You can test for specific errors and rethrow the rest, or catch every possible error in one dragnet, print an error message, and close up. It entirely depends on the type of data you're reading, what errors you will reasonably expect to see, and what level of gracious error recovery you need to do.

As for using break, there is a way to do this without doing a double free, however, it might be unpopular with some people.

Essentially, it looks something like this:

while (someCondition) {
   // Do things
   error = 1; // Error encountered
   if (error)
       goto cleanUp;
   // Do some more things
}
// Do some other things that you need to do outside the loop here
:cleanUp
free(buffer);
return error;

It operates similar to having a custom onError event handler in some languages.

roelofs
  • 2,132
  • 20
  • 25
  • 2
    There is no "cleanup" necessary if this is written in C++ with `std::string` and the standard I/O functions in the first place. Also RAII would make sure that the cleanup is performed, no need for a `goto` – quantdev Aug 13 '14 at 07:50
  • Why not free the buffer, set it to NULL and free it again later. Freeing on NULL pointers is not an issue. – martin Aug 13 '14 at 07:50
  • Like I said, this may not be a popular approach (personally I'm not a fan either) ;) It is quick though, and actually occurs in more production software than you might think. – roelofs Aug 13 '14 at 07:54
  • @quantdev: The reason I use these C functions is due to speed. I need to do a LOT of file accesses, and the best speed is obtained by using these (instead of for example streams). – DoubleYou Aug 19 '14 at 04:14
0

Step one: Deal with errors early, so move the "if we couldn't open the file" up a bit.

bool SomeClass::ReadFile(int ignorelines) {
    FILE *file = fopen(m_filepath.c_str(), "r");
    if (file==NULL) {
      m_error+="There was an error opening the data file.\n";
      return false;
    }

Then the rest of the code use a res variable to track whether the whole operation succeeded or not, and a single if at the end of the sequence to determine if we continue the loop or not (or you could do while (res && fggets( ... )) if you prefer that).

    char *buffer;                           //line reading buffer
    bool result = true;
    while (fggets(&buffer,file)==0) {       //read line (fggets returns non-zero at provided terminator or \eof)
        if (ignorelines>0) {
            ignorelines--;                  //count the lines ignored
        }
        else
        {
            line.assign(buffer);
            if (!PerformLineCheck1(buffer)) {   
                m_error+="The line is not allowed.\n";
               res = false;
            }
            else if (!PerformLineModification1(buffer)) {
                m_error+="The line could not be modified properly.\n";
                res = false;
            } 
            else if (!PerformLineExtraction(buffer)) {   
                m_error+="The variables could not be extracted.\n";
                res = false;
            }
        }
        //extracted all, now continue with next line
        free(buffer);                       //clear up the line
        if (!res) break; 
    }
    fclose(file);
    return res;
}

That reduces the duplication in the code to free and close.

(Edit: Not being a fan of continue, I changed it to else after the first if, and removed that free(buffer) call too)

Mats Petersson
  • 126,704
  • 14
  • 140
  • 227
  • Based on my question this is indeed a nice solution. Unfortunately I have other pieces of code in between the check functions, which are not allowed to be executed if the check function failed. This would then, in your solution, create `if {}`, `else { if(!function()) {} }` blocks. Of course it works, but it's not 'beautiful'... – DoubleYou Aug 19 '14 at 04:22
  • 1
    It's hard to answer a question based on anything other than what is IN the question... However, if you do all the checks first, then you can do `if (res) { ... }; free buffer; if (!res) break;` – Mats Petersson Aug 19 '14 at 07:33
  • I totally agree, of course! I simplified my code for my question probably too much, if I were to post the actual code it would span the page... – DoubleYou Aug 20 '14 at 01:06
0
  1. You can use the try block with just one line, so other functions would not cause any problem, example is shown below.
  2. If you are unsure if a certain buffer is cleared, use the xxx==nullptr to check so it will not be deleted twice.

For point no. 1, If you want to catch the exception while using fgets(), then do this:

int result = 0;
while (true) {
    char* buffer = new char[100]; //YOU MUST allocate space for the buffer, 
                                  //cannot use it directly
    try {
        result = fggets(&buffer,file);
    }
    catch () {
        //do whatever you need to catch, break if necessary
        //handling of memory is shown below
    }
    if (0 == result) {
        if (nullptr != buffer) { // make sure when you execute delete[] or free, the pointer is valid
            delete[] buffer;//or free
        }
        break;
    }
    //do your other things here
}
Leon
  • 338
  • 3
  • 11
  • Don't understand the "danger" that you point out - are you talking about running out of memory, or some other "danger"? – Mats Petersson Aug 13 '14 at 08:01
  • If I understand correctly, the fgets never create a new array for you. It just dump whatever it reads to the array. char* buffer and calling fgets can cause the content to be written to some other memory space that is already in use. Anyway, I don't think the thread starter knows fgets well, here is a link. [link](http://www.cplusplus.com/reference/cstdio/fgets/) – Leon Aug 13 '14 at 08:07
  • Of course, if this code is run in VS, they will tell you use of null pointer – Leon Aug 13 '14 at 08:09
  • Except the OP is not using `fgets`, but `fggets`, which operates differently. It allocates a new string every call, no matter what (follow the link in the original post to a link to a zip file to check the source in ggets.c - I just did) – Mats Petersson Aug 13 '14 at 08:09
  • Your code also doesn't show how to deal with the "if stuff goes wrong after reading the line" - in fact your current code doesn't throw any exception in the `try` block... This is exactly what the OP was asking for a solution of, not "how to read one line". – Mats Petersson Aug 13 '14 at 08:16