开发者

How to make sure that malloc/free fopen/fclose are matching?

开发者 https://www.devze.com 2023-03-12 12:03 出处:网络
I think the following code is normal (and malloc/free is similar): int foo(){ FILE *fp = fopen(\"test.in\", \"r\");

I think the following code is normal (and malloc/free is similar):

int foo(){

   FILE *fp = fopen("test.in", "r");

   int i;   

   for(i = 0; i < NUM; i ++){
       if(Match(fp, i)){    
            fclose(fp);
            return i;
       }
   }

   fclose(fp);
   return 0;
}

As we can see fclose(fp) appears twice in the code. It will appear more if there are other return statements in the function foo. However, it is troublesome that I have to write fclose(fp) many times. One solution is just one return for one function. However, multiple returns is sometimes useful. Is there any other solution?

PS: As I know, there is a macro(with-open-file) in lisp.

(with-open-file (stream-var open-argument*) 
    body-form*)

It coul开发者_开发技巧d open and close file for us.


The use of break often helps:

int foo() {

   FILE *fp = fopen("test.in", "r");

   int i, result;   

   result = 0;
   for(i = 0; i < NUM; i ++){
       if(Match(fp, i)){    
            result = i;
            break;
       }
   }

   fclose(fp);
   return result;
}


In the source code of linux kernel, there are many functions that have to take care of locks and other resource on return. And they conventionally add a cleanup label at the end of the function, and goto there whenever early return occurs. I personally recommend this usage to avoid duplicate code, and maybe this is the only sane usage of goto.


An extra layer of indirection can ensure you don't miss an exit from the function:

int foo(FILE *fp)
{

   int i;   

   for(i = 0; i < NUM; i ++){
       if(Match(fp, i)){    
            return i;
       }
   }

   return 0;
}

int foo_wrapper(void)
{
    FILE *fp = fopen("test.in", "r");
    int out = foo(fp);
    fclose(fp);
    return out;
}


I'll expand on exception handling via goto (mentioned in @Charles Peng's answer):

You do it something like:

int func(...)
{
    ...
    f = fopen(...);
    if (f == NULL) {
            log("failed opening blah blah...");
            goto err;
    }
    ...
    m = malloc(...)
    if (m == NULL) {
            log("blah blah...");
            goto err_close_f;
    }
    ...
    if (do_something(...) < 0) {
            log("blah blah...");
            goto err_close_f;
    }
    ...
    r = alloc_resource(...)
    if (r == 0) {
            log("blah blah...");
            goto err_free_m;
    }
    ...
    return 0;


err_free_m:
    free(m);
err_close_f:
    fclose(f);
err:
    return -1;
}

The advantage of this is that it's very maintainable. Resource acquisition and release has a somewhat symmetrical look when using this idiom. Also, resource release is out of the main logic, avoiding excessive clutter where it annoys the most. It's quite trivial to check that error handling of functions is right (just check that it jumps to the appropiate point, and that the previous label releases any acquired resource)...


A return statement NOT at the end of a function is the equivalent of a goto statement. Even though it may appear as though some functions are simpler with multiple return statements it has been my experience while maintaining various code bases that the ones with only 1 exit point from every function are easier to maintain.

0

精彩评论

暂无评论...
验证码 换一张
取 消