Why are stderr, stdin, stdout defined as macros?
Asked Answered
U

3

5

C99 defines these three as macros that "are expressions of type ‘‘pointer to FILE’’ that point to the FILE objects associated, respectively, with the standard error, input, and output streams".

Given this, these expressions may or may not be const (i.e. the pointer may or may not be written to redirect to another file or stream); and they may have either internal or external linkage (i.e. writing to redirect to another file or stream may not persist between two separately-compiled C files). Therefore, attempting to redirect stdout from within a C file may have unexpected behavior.

(Note: In gcc-9.4.0, it appears that they are macros mapped to a non-const variable with external linkage having the same name and accompanied by the comment: /* C89/C99 say they're macros. Make them happy. */.)

Can anyone shed some light on whether there is an official reason for this or is it safe to assume that this is another "oversight" in the original standard and that redirecting the streams should be avoided from within C code in order to keep things portable?

Uncertainty answered 30/5, 2023 at 13:8 Comment(5)
As the macros are defined then the pointer expressions can not be with the qualifier const or have internal linkage. The macros are predefinedBallesteros
It's important to note, that the exact nature of these macros, such as whether they are const or have internal or external linkage, is not specified by the C standard. It's implementation-dependent and can vary across different compilers and platforms. While these macros provide convenience, it's generally recommended to use the standard I/O functions and appropriate stream redirection functions for more portable and flexible input/output operations in C code as you suggested. I'm unsure of the exact reasons, but it's probably just a historic holdoverBelinda
Using #define stdout stdout is kosher. The reason is most probably historical. For a long time (197x-200x), most systems used macros like #define stdin _ios[0] (or possibly &_ios[0] — the name was in the "reserved for the system" namespace). This meant that they could be used in file stream initializers at global scope (static FILE *err = stderr;). GNU C Library changed their definition sometime in the 'oughts' and that's no longer possible.Loella
The macros definitely have neither internal nor external linkage. All macro identifiers have no linkage. It is possible, however, that stdin etc are also object identifiers, and if so, those identifiers will have either internal or external linkage.Perfidy
A C program can call freopen to redirect the standard streams.Oeuvre
P
9

these expressions may or may not be const (i.e. the pointer may or may not be written to redirect to another file or stream);

Correct.

and they may have either internal or external linkage

The macro identifiers themselves have no linkage, and only if their replacement texts are identifiers is linkage even meaningful for them.

[...] Therefore, attempting to redirect stdout from within a C file may have unexpected behavior.

No. Attempting to assign a new value to stdout may fail or have unexpected results. But yes, you have discovered that performing such an assignment is not a reliable way to change the destination to which stdout is connected.

Can anyone shed some light on whether there is an official reason for this

There is an official rationale document for C99. It does not address this point.

or is it safe to assume that this is another "oversight" in the original standard

I don't know what you mean. I think it's quite safe to assume that stdin, stdout, and stderr were specified to be macros quite intentionally. And it follows directly from their specifications that you cannot rely on assigning to them, for any purpose.

I infer that they are specified as they are to allow flexibility to implementations. For example, they could be implemented as function calls or as table lookups. They don't have to be object identifiers.

and that redirecting the streams should be avoided from within C code in order to keep things portable?

No. Assigning to stdin, stdout, and stderr must be avoided for strict conformance to the C language specification (which roughly equates to "to keep things portable"). If you want to associate one of the standard streams with a different destination, then that is the primary purpose of the freopen() function, available in all versions of standard C.

Perfidy answered 30/5, 2023 at 14:30 Comment(3)
Note that the standard says (C11 §7.21.1.3 Files ¶6): The address of the FILE object used to control a stream may be significant; a copy of a FILE object need not serve in place of the original. IOW: you can't simply assign or copy the FILE structures.Loella
True, @JonathanLeffler, but I don't think anyone suggested doing that. As I know you know, stdin, stdout, and stderr expand to expressions of type FILE *, not FILE, and the OP's complaint is that they cannot portably assign the address of a different FILE to one of these.Perfidy
@JohnBollinger - Makes sense. I would not that when I mentioned "internal or external linkage", I was referring to identifiers in the expression that would be expanded by the pre-compiler. The reason I brought this up, is I have seen blogs where others assign to stdout to redirect it and I wanted to dig in a bit. In my case, I was looking to temporarily reroute stdout for testing then route it back and wanted an easy way to do this, but I ultimately decided to wrap vfprintf inside of a function so that I can switch between streams.Uncertainty
V
7

It's this way for historical reasons.

You can't reassign stdin, stdout, or stderr. If you need to reopen these streams so they do their i/o somewhere else, that's what freopen is for.

In the earliest implementations of <stdio.h>, there was a global array

FILE _iobf[20];

and then stdin, stdout, and stderr were, yes, macros:

#define stdin &_iobf[0]
#define stdout &_iobf[1]
#define stderr &_iobf[2]

This worked fine, and it was nice and compact. It looks "weird" to our eyes today — "Why would anyone have written it that way?", you might ask — but the point is that they did write it that way, and it stayed that way for many years, long enough that the C Standard had to accommodate it, by forbidding conforming programs from assigning new values to those "variables".

(To say a little more about "Why would anyone have written it that way?", I believe the answer is, economy. When <stdio.h> was invented, C was brand-new, and the PDP-11 had a 64k address space, and you couldn't even always use all of it. Everybody did raw I/O using read() and write(), or implemented their own, special-purpose buffering schemes. Some people asked, "Why do we have to implement our own? Why isn't there a standard one?", while others said, "Everybody's needs are sightly different; no one implementation could satisfy everyone." It was a lot like the discussions we're still having today about why C doesn't have, say, a standard linked-list implementation. At some point Mike Lesk declared that he was going to implement a standard I/O library, that it was possible to come up with one implementation that would satisfy everyone's needs, or well enough. But to placate the naysayers, and to not inconvenience big programs that were already bumping up against the 64k ceiling, there was a huge incentive to keep the memory footprint — both code and data — minimal. I don't remember all the details, but that FILE _iobf[20]; scheme really did end up being pretty darn compact, and I'm guessing that any "obviously better" scheme you might come up with — that involved, say, dynamically allocating FILE structs rather than having a fixed array of 20 of them — would have a significantly larger memory footprint.)

Verb answered 30/5, 2023 at 14:41 Comment(0)
G
0

There are not so many options. What are the options?

  1. a language keyword
  2. a predefined enum (or a number/id/constant)
  3. a variable
  4. a pointer
  5. a macro

Let's go through these options.

  1. A language keyword is nothing. A language keyword after being parsed and compiled is pure semantic, but not something you can pass around or use as an argument for printf.

  2. An enum like enum std { out, in, err } would work. Then you have numbers. This has the disadvantage that there is an extra printf needed with an integer as first argument. Printing something to a FILE would be a different procedure than printing someething to stdout.

  3. Problematic with a (global) variable is that it has to exist. It must be citizen of your program. It must be initialised. In a language that is used a lot for embedded software, where you want to have full control about what happens, this is no good option.

  4. Normally stdout, stdin, stderr all refer to pointers internally. But making them pointing to something by standard is no good idea. There are plattforms where IO is actually not via buffers and pointers. For example if the output is directly sent to an RS232 port. Giving the developers a pointer in the hand would give them the wront expectation that there is actually something behind that pointer. But that is not the case. FILE is unspecified in the standard.

  5. Finally it is a macro. That gives the compiler authors the freedom to let stdout, stdin, stderr be whatever technically makes sense. The developers does not get any promise about them.

And why does printf uses a pointer to FILE and not a (global) FILE object? Stdout must still manifested in something. The macro must expand to something. If it expands to an object, that objext must exist somewhere. It would be very unconvenient for platforms, where stdout is not an object, but a buffer. And for platforms where it is nothing (nothing else than a magic number), it would also not work. You could not even compare if stdin is the same than stdout.

A generic pointer type (as FILE is unspecified by the standard) unifies everything you can imagine. The standard must agree on something, that works in all situations. And that is a pointer of an unspecified type.

Although these considerations seem to be very artifiial, they matter. Imagine you have a platform where stdout is sending out morse coded data via a green LED and sterr is sending out morse coded data via a red LED. There is no buffer. What would you want to have in order to distinguish stdout from stderr. You need a boolean only. But you don't really want to store somewhere a number 0 and a number 1 and point to that. No, you pass two constants. Two magic numbers. And that can be done as pointer types, even if they are not really pointer. But the standard does not promise you that there is anything useful behind these fake pointer.

Again, why not pointers without the macro? Because what you pass must be of the type FILE*. If you have a pointer of type FILE*, you can dereference it. But compiler authors may decide that stdout is a very special thing, which is not allowed to be dereferenced. If they only give you a macro without telling you what the macro is, they don't promise you that you can dereference what the macro yields. All the standard promisses you is that the macros are expanded to "expressions of type FILE*", but not that they are expanded to something, that actually points to a FILE. That description is the contract that you agree when calling the macro. If you just use a predefined pointer type constant maginc number you don't explicitly agree to any such contract. So you are allowed to dereference it, because the promise of static typing is that the type specifies what you are working with.

Gearing answered 3/9, 2024 at 22:27 Comment(4)
If you have a pointer of type FILE*, you can dereference it. No, that need not be true. See opaque pointer.Comptroller
But opaque pointers are for situation where the actual type is unknown. So you cannot dereference it, because you don't know the type. If you have that missing information, you are also allowed to dereference. In the situation I explain, when you have a compiler like the gcc, it is well known what TYPE is, so you should be allowed to dereference. If you see the example from Wikipedia, in obj.c you are allowed to dereference. But you are never allowed to dereference stdout even if you know its type.Gearing
when you have a compiler like the gcc, it is well known what TYPE is, so you should be allowed to dereference No, it's not "well known", because there is no requirement in C that FILE * can be dereferenced in any context, and there are more than a few systems that do not provide a FILE * definition, only an opaque type.Comptroller
I was not aware of that. Thank you! This is why they are macros. So that nobody tries to dereference them. But something there is. There is not nothing in the gcc. So you can convert the pointer into your favourite pointer type and dereference it. In the gcc you don't get a memory violation if you read from that address. It is legal to dereference the pointer. A macro you cannot dereference like this, because the standard does not tell you what the macro expands to. The standard only clarifies what the type of that expression is. But it does not tell you that you get a pointer.Gearing

© 2022 - 2025 — McMap. All rights reserved.