简体   繁体   中英

check if FILE* is stdout; portable?

I am currently writing a piece of code whose intended usage is this:

program input.txt output.txt

or

program input.txt

in which case it defaults to stdout .

This is the code I have now (within main() ):

FILE *outFile;
if (argc < 3) {
        outFile = stdout;
    } else {
        fprintf(stdout, "Will output to file %s\n", argv[2]);
        outFile = fopen(argv[2], "w");
        if (outFile == NULL) {
            fprintf(stderr, "ERR: Could not open file %s. Defaulting to stdout\n", argv[2]);
            outFile = stdout;
        }
    }
/* ... write stuff to outFile... */
if (argc < 3 && outFile != stdout) {
        fclose(outFile);
    }

These are my concerns: first of all, will this successfully open and close outFile when provided? Also, will this successfully not close stdout? Can anything bad happen if I close stdout?

Also, is this portable? I compile with gcc but this project will be evaluated by a professor using Windows.

Apologies if this is a bit of a mess of a question. I come from Python and am not a CS major (I'm studying mathematics).

是的,它是便携式的,还可以。

Yes, it's portable. You assigned outfile = stdout , so they will be equal as long as you don't reassign either of them elsewhere in the program.

You don't really need the argc < 3 test as well -- the two conditions should always be the same, since you only do the assignment when that's true.

In any program that writes significant data to stdout , you should close stdout immediately before exiting, so that you can check for and report delayed write errors. (Delayed write errors are a design mistake; it ought to be impossible for fclose or close to fail. But we are stuck with them.)

The usual construct is, at the very end of main ,

if (ferror(stdout) || fclose(stdout)) {
    perror("stdout: write error");
    return 1;
}
return 0;

Some programs stick an fflush in there too, but ISO C requires fclose to perform a fflush , so it shouldn't be necessary. This construct is entirely portable.

It's important for this to be the very last thing you do before exiting. It is relatively common for libraries to assume that stdout is never closed, so they may malfunction if you call into them after closing stdout . stdin and stderr are also troublesome that way, but I've yet to encounter a situation where one wanted to close those.

It does sometimes happen that you want to close stdout before your program is completely done. In that case you should actually leave the FILE open but close the underlying "file descriptor" and replace it with a dummy.

int rfd = open("/dev/null", O_WRONLY);
if (rfd == -1) perror_exit("/dev/null");
if (fflush(stdout) || close(1)) perror_exit("stdout: write error");
dup2(rfd, 1);
close(rfd);

This construct is NOT portable to Windows. There is an equivalent, but I don't know what it is. It's also not thread-safe: another thread could call open in between the close and dup2 operations and be assigned fd 1, or it could attempt to write something to stdout in that window and get a spurious write error. For thread safety you have to duplicate the old fd 1 and close it via that handle:

// These allocate new fds, which can always fail, e.g. because
// the program already has too many files open.
int new_stdout = open("/dev/null", O_WRONLY);
if (new_stdout == -1) perror_exit("/dev/null");
int old_stdout = dup(1);
if (old_stdout == -1) perror_exit("dup(1)");

flockfile(stdout);
if (fflush(stdout)) perror_exit("stdout: write error");
dup2 (new_stdout, 1); // cannot fail, atomically replaces fd 1
funlockfile(stdout);

// this close may receive delayed write errors from previous writes
// to stdout
if (close (old_stdout)) perror_exit("stdout: write error");

// this close cannot fail, because it only drops an alternative
// reference to the open file description now installed as fd 1
close (new_stdout);

Order of operations is critical: the open , dup and fflush calls must happen before the dup2 call, both close calls must happen after the dup2 call, and stdout must be locked from before the fflush call until after the dup2 call.

Additional possible complications, dealing with which is left as an exercise:

  • Cleaning up temporary fds and locks on error, when you don't want to stop the whole program on error
  • If the thread might be canceled mid-operation
  • If a concurrent thread might call fork and execve mid-operation

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM