Files & dirs: historical reasons?

K

Kenneth Brody

pete said:
Kenneth Brody wrote: [...]
I don't believe that the standard requires that they be macros. They
simply need to be of type FILE*.

(I'm going by memory here, as I believe someone else quoted C&V about
this in the not-too-distant past.)

I did.

Within this part of the ISO/IEC 9899: 1990 standard:
7.9 Input/output <stdio.h>
7.9.1 Introduction
you should locate this phrase: "The macros are",
and then you should keep reading until
you come to the period at the end of the sentence.

Apparently, my mind isn't as sharp as it once was. (At least, I
think it once was sharp.)

Do you know how hard it is to stick a size 14 foot in one's mouth?

--
+-------------------------+--------------------+-----------------------+
| Kenneth J. Brody | www.hvcomputer.com | #include |
| kenbrody/at\spamcop.net | www.fptech.com | <std_disclaimer.h> |
+-------------------------+--------------------+-----------------------+
Don't e-mail me at: <mailto:[email protected]>
 
C

CBFalconer

Random832 said:
#define getc(F) fgetc(F) // I refute it thus.

Thus giving up the considerable advantages of implementing getc as
a proper macro. The point is to avoid the overhead of function
calls, and to directly access the streams internal buffers.
 
R

Random832

2006-12-20 said:
Thus giving up the considerable advantages of implementing getc as
a proper macro. The point is to avoid the overhead of function
calls, and to directly access the streams internal buffers.

That's no reason to _require_ FILE to be a complete type - if an
implementation wants to do the sort of trickery that requires that,
fine, it can make FILE a complete type. If it doesn't, let it use an
incomplete type, or even void.

There is absolutely no reason for the following to be considered
a conforming program.

#include <stdio.h>
int main() {
FILE x = *stdin;
return 0;
}
 
R

Richard Tobin

#define getc(F) fgetc(F) // I refute it thus.
[/QUOTE]
Thus giving up the considerable advantages of implementing getc as
a proper macro. The point is to avoid the overhead of function
calls, and to directly access the streams internal buffers.

FILE still doesn't need to be a complete type. There could instead
be an implementation-private type:

#define getc(F) (((struct _FILE *)(F))->_count > 0 ? ...)

Of course that has the disadvantage that

char *p;
getc(p);

will not produce a compile-time error. The compile-time error is
likely to be rather obscure, though.

-- Richard
 
P

Peter Nilsson

Random832 said:
That's no reason to _require_ FILE to be a complete type

Perfectly true, but what of it? Any practical implementation is going
to
declare FILE as a complete type anyway.
- if an
implementation wants to do the sort of trickery that requires that,
fine, it can make FILE a complete type. If it doesn't, let it use an
incomplete type, or even void.

But what implementation would go out its way to make FILE opaque?

You seem to be under the delusion that the purpose of the standards
is to (re)invent C as some perfect abstract language.

The real purpose of standardisation, particularly C89, was to define
existing common practice. That included leaving in a great many number
of warts. Despite (in some cases even because of) those warts, C has
become a very successful language.

Of course it's not too late to change it, but to quote Doug Gwyn on the
issue:

"to do anything with an actual structure the type has to be
complete."

"Before trying to 'fix' anything, we need a good demonstration
of what is *broken*. Just because some legacy interface does
not meet your current idea of good style is insufficient
reason to change it - *especially* stdio."
 
K

Keith Thompson

FILE needs to be a complete type because otherwise you could not e.g.
implement getc as a macro.

Yes, you could. FILE could be a typedef for void, making FILE*
equivalent to void*. A getc() macro could convert its FILE* argument
to a pointer to some object type. Or it could all be done by compiler
magic.

There are good reasons to make FILE a complete type; it makes getc()
and friends easier to write. There are no good reasons to *require*
FILE to be a complete type in all implementations. On the other hand,
the requirement, though useless, is not burdensome.
 
K

Keith Thompson

Peter Nilsson said:
Perfectly true, but what of it? Any practical implementation is
going to declare FILE as a complete type anyway.


But what implementation would go out its way to make FILE opaque?

Poorly written code might attempt to refer to system-specific members
of FILE. Such code is non-portable, but the compiler most likely will
not be able to diagnose it. An implementation might make FILE opaque
to prevent users from making this error.

Note: I am not arguing that an implementation is required to do this,
or even that it should; merely that it's not unreasonable to do so.
You seem to be under the delusion that the purpose of the standards
is to (re)invent C as some perfect abstract language.

The real purpose of standardisation, particularly C89, was to define
existing common practice. That included leaving in a great many number
of warts. Despite (in some cases even because of) those warts, C has
become a very successful language.
[...]

Sure, but this particular wart, though minor, is a completely
unnecessary one. If the standard has removed the requirement for FILE
to be an object type, it needn't have affected any implementation or
any reasonable program.
 
C

CBFalconer

Keith said:
Yes, you could. FILE could be a typedef for void, making FILE*
equivalent to void*. A getc() macro could convert its FILE*
argument to a pointer to some object type. Or it could all be
done by compiler magic.

And how does that 'object type' know what, where and how big the
fields are?
There are good reasons to make FILE a complete type; it makes
getc() and friends easier to write. There are no good reasons to
*require* FILE to be a complete type in all implementations. On
the other hand, the requirement, though useless, is not burdensome.

s/easier to write/possible to write as macros/
 
R

Richard Tobin

Yes, you could. FILE could be a typedef for void, making FILE*
equivalent to void*. A getc() macro could convert its FILE*
argument to a pointer to some object type. Or it could all be
done by compiler magic.
[/QUOTE]
And how does that 'object type' know what, where and how big the
fields are?

By being a struct exactly the same as FILE would have been if it hadn't
been void instead.

-- Richard
 
K

Kenneth Brody

Peter said:
Perfectly true, but what of it? Any practical implementation is going
to declare FILE as a complete type anyway.


But what implementation would go out its way to make FILE opaque?

If, for some obscure reason, an implementation wanted/needed to have
an opaque FILE, it could always do something like:

typedef struct
{
int foo;
char *bar;
struct opaque_FILE *opaque;
}
FILE;

Or is this considered "incomplete" because of the opaque pointer
within it?

[...]
Of course it's not too late to change it, but to quote Doug Gwyn on the
issue:

"to do anything with an actual structure the type has to be
complete."

"Before trying to 'fix' anything, we need a good demonstration
of what is *broken*. Just because some legacy interface does
not meet your current idea of good style is insufficient
reason to change it - *especially* stdio."


--
+-------------------------+--------------------+-----------------------+
| Kenneth J. Brody | www.hvcomputer.com | #include |
| kenbrody/at\spamcop.net | www.fptech.com | <std_disclaimer.h> |
+-------------------------+--------------------+-----------------------+
Don't e-mail me at: <mailto:[email protected]>
 
K

Keith Thompson

By being a struct exactly the same as FILE would have been if it hadn't
been void instead.

Exactly.

User code trying to refer to the fields of this structure would have
to make explicit references to some obviously internal name like
"struct __FILE"; programmers doing so would have no grounds for
complaint when their code stops working. The implementation of the
getc() would have to do the same thing, but it's *expected* to be
system-specific.

For that matter, even with the current rules, FILE could be some
opaque type. It could even be a typedef for, say char; since char*
and void* are similar in many ways, this would be just about like
making FILE a typedef for void; a sufficiently perverse programmer
could try to play with the value of, say, *stdin, but would most
likely get the unpredictable results he deserves.

On the other hand, I've rarely seen user code that tries to look at
the innards of type FILE anyway. I don't even know what's in it on
the systems I use. Type FILE is *effectively* opaque as long as
programmers don't try to look at it.
 
C

CBFalconer

R

Richard Bos

CBFalconer said:
Please describe exactly how you do that, in a portable manner.

Who cares about portable? getc() is part of the implementation. A getc()
macro must behave in a portable manner, but it needn't be implemented
portably. It must work with the implementation it is part of, and
exhibit portable behaviour, that's all.
(IOW, it could be as simple as

#define getc(FILE *f) ( ((char *)f)[4]? -42: ((unsigned char *)f)[3] )

for an implementation for which those magic numbers incant the correct
spell.)

Richard
 
C

CBFalconer

Richard said:
CBFalconer said:
Please describe exactly how you do that, in a portable manner.

Who cares about portable? getc() is part of the implementation. A
getc() macro must behave in a portable manner, but it needn't be
implemented portably. It must work with the implementation it is
part of, and exhibit portable behaviour, that's all.
(IOW, it could be as simple as

#define getc(FILE *f) ( ((char *)f)[4]? -42: ((unsigned char *)f)[3] )

for an implementation for which those magic numbers incant the
correct spell.)

You are right. But it would be a maintenance nightmare.
 
K

Keith Thompson

CBFalconer said:
Please describe exactly how you do that, in a portable manner.

Please don't remove attributions for material you quote.

(I wrote the stuff above starting with "Yes, you could.")

The internals of type FILE are inherently system-specific, so I can't
describe *exactly* how to do that "in a portable manner". But I can
give an example.

Here's a definition of the getc() macro (from Solaris):

#define getc(p) (--(p)->_cnt < 0 ? __filbuf(p) : (int)*(p)->_ptr++)

So apparently type FILE has members called "_cnt" and "_ptr"; __filbuf
is a function that takes a FILE* argument.

Suppose that (in violationof the C standard) this implementation had:

struct __FILE { blah blah };
typedef void FILE;

where struct __FILE has the same fields that FILE has in real life.
Assume also that __filbuf takes a struct __FILE* argument.

Then the definition of getc() could be:

#define getc(p) (--((struct __FILE*)p)->_cnt < 0 ? \
__filbuf((struct __FILE*)p) : \
(int)*((struct __FILE*)p)->_ptr++)

(*if* I've gotten it right).

The actual functions that take FILE* arguments would also have to be
modified.

It makes <stdio.h> more complicated, but who cares? And it makes it
slightly more difficult for perverse user code to use internals that
it shouldn't know about.
 
R

Richard Bos

CBFalconer said:
Richard said:
CBFalconer said:
Richard Tobin wrote:

Yes, you could. FILE could be a typedef for void, making FILE*
equivalent to void*. A getc() macro could convert its FILE*
argument to a pointer to some object type. Or it could all be
done by compiler magic.

And how does that 'object type' know what, where and how big the
fields are?

By being a struct exactly the same as FILE would have been if it
hadn't been void instead.

Please describe exactly how you do that, in a portable manner.

Who cares about portable? getc() is part of the implementation. A
getc() macro must behave in a portable manner, but it needn't be
implemented portably. It must work with the implementation it is
part of, and exhibit portable behaviour, that's all.
(IOW, it could be as simple as

#define getc(FILE *f) ( ((char *)f)[4]? -42: ((unsigned char *)f)[3] )

for an implementation for which those magic numbers incant the
correct spell.)

You are right. But it would be a maintenance nightmare.

True. Then again, reading some of the implementation headers on my
machine, I'm not sure any of their authors would notice; and you could
make it more readable with a few judicious #defines.

Richard
 
R

Richard Tobin

By being a struct exactly the same as FILE would have been if it
hadn't been void instead.
[/QUOTE]
Please describe exactly how you do that, in a portable manner.

I've no idea what you're getting at. We're talking about the
implementation of the standard library, so where does "portable" come
into it?

Anyway, as I said before, instead of defining a complete type FILE,
you would define, say, _FILE (which is in the implementation
namespace), and cast getc()'s FILE * argument to _FILE *. The details
of _FILE would be completely implementation dependent.

-- Richard
 
R

Random832

2006-12-22 said:
Please describe exactly how you do that, in a portable manner.

"portable" has nothing to do with how an implementation may do things.
We're talking about how a getc() macro can be implemented when FILE *
itself is an opaque pointer (i.e. typedef void FILE;)

There's no reason the getc macro can't cast its argument to a "struct
_FILE *" and then reference things through it that way, without FILE
itself being a typedef for struct _FILE.
 
S

SuperKoko

Random832 said:
The standard requires some things that are utterly pointless. Requiring
that stdin/stdout/stderr are macros is one of these. Requiring that FILE
be a complete type is another.

Useless for modern programs, yes.
But, utterly pointless, no.

The standard specifies such things for compatibility with legacy code.
Probably small amounts of legacy code. But, since it doesn't harm,
maintaining backward compatibility is a good thing.

For stdin, stdout and stderr, requiring that they be macros (instead of
simply allowing it) allow some programs to do things such as:

#include <stdio.h>

#undef stdin
#define stdin my_stdin

If the standard had specified that stdin can be a macro but is not
guaranteed to be one, an easy workaround would have been:

#include <stdio.h>

#ifdef stdin
#undef stdin
#endif

#define stdin my_stdin

But it would have broken legacy code.... Plus, it simplifies a bit more
modern programs that want to #undef stdin... And it avoids that, guys
who doesn't know well the standard, accidentally write non-portable
code, relying on the fact that their particular compiler define stdin
as a macro.

Such code does exist:

http://www.google.com/codesearch?hl=en&q=#undef\+stdin$

The fact that FILE has to be a complete type, is probably used in
legacy code for things like:

FILE Special; /* won't be initialized to anything particular, but that
doesn't matter */
/* this FILE is only designed to have an address distinct from all
addresses of real FILE structures */
void Function(FILE* f) {
if (f==&Special) {
/* handle this case specially */
}
}

/* invocation examples */
Function(stdout);
Function(NULL); /* does another thing */
Function(&Special); /* again, treated differently */


This programming style is *very* ugly, yet the standard doesn't want to
break gratuitously legacy code, even when it has very bad style.

Of course, FILE being a complete type, doesn't implies that it can't be
opaque.
The definition of FILE may be:

typedef struct {char __dummy;} FILE;

Note: If getc is implemented as a macro, it may use a typecast from
FILE* to _FILE* where _FILE is a complete type containing the real
fields used by the compiler internally.
It won't really hide the structure from malicious programmers, but it
will prevent the innocent programmer from accidentally accessing fields.
 
G

Guest

SuperKoko said:
Useless for modern programs, yes.
But, utterly pointless, no.

The standard specifies such things for compatibility with legacy code.
Probably small amounts of legacy code. But, since it doesn't harm,
maintaining backward compatibility is a good thing.

For stdin, stdout and stderr, requiring that they be macros (instead of
simply allowing it) allow some programs to do things such as:

#include <stdio.h>

#undef stdin
#define stdin my_stdin

If the standard had specified that stdin can be a macro but is not
guaranteed to be one, an easy workaround would have been:

#include <stdio.h>

#ifdef stdin
#undef stdin
#endif

#define stdin my_stdin

Even if stdin is not defined as a macro, the first form is valid.
#undef has no effect if the identifier is not a macro name.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,433
Messages
2,571,683
Members
48,796
Latest member
Greg L.

Latest Threads

Top