2
\$\begingroup\$

I thought about using a macro to auto close FILE pointers at the end of a block. My solution so far is:

FILE *fopen_safe(const char *filename, const char *mode) {
    FILE *fp = fopen(filename, mode);

    if (!fp) {
        perror("fopen");
        exit(EXIT_FAILURE);
    }

    return fp;
}

#define with_fopen(fp, filename, mode) \
    for (FILE *fp = fopen_safe(filename, mode), _invariant = { ._r = 1 }; _invariant._r--; fclose(fp))


// Usage:
with_fopen(fp, "test", "w") {
    fprintf(fp, "test\n");
}

This solution is not elegant as i am using an int in the struct to create my loop invariant.

Is there another way to auto close files? Can a block be associated with a macro in a smarter way?

\$\endgroup\$
2
  • \$\begingroup\$ Is there another way to auto close files? Port your code to C++ :-) C really can't do this in any reasonable way. Just a fact of life. \$\endgroup\$ Mar 26, 2016 at 4:26
  • \$\begingroup\$ Use function pointers along with a void * to fake closures. Not nice, but works unless you're using longjmp. \$\endgroup\$ Mar 26, 2016 at 6:24

2 Answers 2

4
\$\begingroup\$

There are few problems with this macro.

  • It relies on an undocumented layout of FILE structure
  • It cannot be nested (try it with -Wshadow)
  • It assumes that the block is always left through the end
  • It may create suprises:

        for (....) {
            with_fopen(fp, ...) {
                int rc = do_something(fp);
                if (rc == -1) {
                    break;
                }
            }
        }
    

    The programmer (with or without Python background) expects the outer loop to be broken. Instead the loop hidden inside with is.

Nice try however.

\$\endgroup\$
3
\$\begingroup\$

Unfortunately, C just isn't designed for this kind of thing. Or more accurately, C can do this perfectly fine, but not within what is commonly called "structured programming."

In C++, the correct answer would be RAII. In Java, it would be try-with-resources. In C, we have none of those things. Instead, the least bad way to do cleanup code in C is with goto:

int func(/* arguments */) {
    int rc = 0;
    char *buf = NULL;
    FILE *fp = fopen(/*...*/);
    if(!fp){
        rc=errno;
        goto err_fp;
    }
    buf = malloc(/*...*/);
    if(!buf){
        rc=errno;
        goto err_buf;
    }
    /* Put some code here
     * In this block, don't write return retval;
     * Instead, write rc=retval; goto out;
     */
out:
    free(buf);
err_buf:
    fclose(fp);
    /* fclose() error checking elided for simplicity.
     * You have to call it in a loop to handle EINTR correctly.
     */
err_fp:
    return rc;
}

This ensures that buf is only freed if it was successfully malloc'd,* fp is only closed if it was opened, and all resources are cleaned up on every code path.

* free(NULL) is legal and harmless. In real code, you should take advantage of this (e.g. by merging the err_buf label into the out label). I'm only using malloc/free as an example to show how this pattern nests.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.