vonbrand
vonbrand

Reputation: 11791

Initialize a `FILE *` variable in C?

I've got several oldish code bases that initialize variables (to be able to redirect input/output at will) like

FILE *usrin = stdin, *usrout = stdout;

However, gcc (8.1.1, glibc 2.27.9000 on Fedora rawhide) balks at this (initializer is not a compile time constant). Rummaging in /usr/include/stdio.h I see:

extern FILE *stdin;

/* C89/C99 say they're macros. Make them happy */

#define stdin stdin

First, it makes no sense to me that you can't initialize variables this (rather natural) way for such use. Sure, you can do it in later code, but it is a nuisance.

Second, why is the macro expansion not a constant?

Third, what is the rationale for having them be macros?

Upvotes: 1

Views: 2238

Answers (2)

Jonathan Leffler
Jonathan Leffler

Reputation: 753695

Classically, the values for stdin, stdout and stderr were variations on the theme of:

#define stdin (&__iob[0])
#define stdout (&__iob[1])
#define stderr (&__iob[2])

These are address constants and can be used in initializers for variables at file scope:

static FILE *def_out = stdout;

However, the C standard does not guarantee that the values are address constants that can be used like that C11 §7.21 Input/output <stdio.h.>:

stderr, stdin, stdout
which are expressions of type ''pointer to FILE'' that point to the FILE objects associated, respectively, with the standard error, input, and output streams.

Sometime a decade or more ago, the GNU C Library changed their definitions so that you could no longer use stdin, stdout or stderr as initializers for variables at file scope, or static variables with function scope (though you can use them to initialize automatic variables in a function). So, old code that had worked for ages on many systems stopped working on Linux.

The macro expansion of stdin etc is either a simple identity expansion (#define stdin stdin) or equivalent (on macOS, #define stdout __stdoutp). These are variables, not address constants, so you can't copy the value of the variable in the file scope initializer. It is a nuisance, but the standard doesn't say they're address constants, so it is legitimate.

They're required to be macros because they always were macros, so it retains that much backwards compatibility with the dawn of the standard I/O library (circa 1978, long before there was a standard C library per se).

Upvotes: 3

user149341
user149341

Reputation:

First, it makes no sense to me that you can't initialize variables this (rather natural) way for such use.

Second, why is the macro expansion not a constant?

stdin, stdout, and stderr are pointers which are initialized during C library startup, possibly as the result of a memory allocation. Their values aren't known at compile time -- depending on how your C library works, they might not even be constants. (For instance, if they're pointers to statically allocated structures, their values will be affected by ASLR.)

Third, what is the rationale for having them be macros?

It guarantees that #ifdef stdin will be true. This might have been added for compatibility with some very old programs which needed to handle systems which lacked support for stdio.

Upvotes: 6

Related Questions