Extent of the "as-if" rule

D

Dan Pop

In said:
Wojtek Lerch wrote:
...

That's not correct - such a re-write would not handle correctly calls to
main() frome within user code (which are legal). It's more accurate to
say that the initial call to main() is essentially equivalent to

exit(main(argc,argv));

ONLY if main is defined as returning a type compatible with int.
In which case, the C99 compiler must insert a return 0; statement at the
very end of main, if the program execution could reach its terminating }.

Dan
 
K

Keith Thompson

pete said:
If puts is defined this way:

#include <stdio.h>
int puts(const char *s)
{
return fputs(s, stdout) > -1 ? fputc('\n', stdout) : EOF;
}

... is there any way that the call to fputc in the above definition,
could return EOF ?

I think that there isn't, because the operation of the standard
output stream has already been verified by the call to fputs,
and the '\n' argument shouldn't cause any problems either.

There are a number of ways the fputc() call could fail, even after the
fputs() call succeeds. The fputs() call doesn't "verify the output
stream", it just verifies that the fputs() call itself succeeded.

Examples: you could run out of disk space or quota, you could hit a
bad disk block, the device you're writing to could lose power between
the fputs() and the fputc(), etc., etc.
 
L

lawrence.jones

In comp.std.c pete said:
If puts is defined this way:

#include <stdio.h>
int puts(const char *s)
{
return fputs(s, stdout) > -1 ? fputc('\n', stdout) : EOF;
}

... is there any way that the call to fputc in the above definition,
could return EOF ?

Yes, lots. Perhaps the fputc() call causes the buffer to be flushed and
the disk is full. Perhaps the disk filled up between the fputs() and
the fputc().

And note that there's no good reason to use fputc() rather than putc()
in this case. (In fact, there almost never a good reason to use fputc).

-Larry Jones

I don't see why some people even HAVE cars. -- Calvin
 
M

Martin Dickopp

Could you post the chapter and verse? I can't find any mention that
open() in read mode modifies anything in the file inode.

On looking it up I found that it in fact doesn't. That should teach me
not to post off-topically here. :)

Martin
 
D

Douglas A. Gwyn

pete said:
... is there any way that the call to fputc in the above definition,
could return EOF ?

Sure, for example the pipe might get closed before the call,
or the file system might fill up, or the user might hit his
size quota. Asynchronous interrupts might have been another
possibility, but properly coded stdio implementations deal
correctly with those (to the extent that restarting a
partial transfer *can* be "correct").
 
D

Dan Pop

Michael said:
[examining /proc/<pid>/fd/*]
That ought to work on a Linux system with the proc filesystem mounted,
for example, though I haven't actually tried it.

[OT]
...except here. In /proc, the directories (which are named after
currently existant PIDs and contain files and directories containing
info relative to those processes) are created and destroyed along with
the processes. Which means that in a system with an uptime of more than
a few moments, they wouldn't exist in sequential order. In fact, the
`array' is quite sparse most of the time.

#include <stdio.h>
int main(void)
{
FILE *fd = fopen("/proc/self/fd/0", "r");
if (fd) puts("This process has an open descriptor 0");
fclose(fd);

Undefined behaviour if fopen() fails. Either drop the unnecessary
fclose call or execute it as part of the the if statement.
return 0;
}

appears to work on my Linux system, and does not appear to me to
be nonconforming; it uses only standard library calls. Extending
it to iterate through, say, the first 10 descriptor numbers to
find out how many files the process has open (up to 10) is trivial.
That in turn would allow it to detect the side effect of opening a
file for reading.

Of course, this is just intended as a real-world example of a
hypothetical implementation which would allow a program to detect
a side effect of such an fopen, and have output that depended on
it, resulting in a difference in program output if the fopen were
optimized away.

It doesn't matter, as long as the program's output is not specified by
the C language. A compiler could optimise a correct version of your
program to:

int main(void)
{
return 0;
}

without having its conformance affected (the standard doesn't guarantee
the success of *any* fopen call).

People need to understand that certain aspects of an implementation's
behaviour are not controlled by the C language definition (consider the
same program compiled and executed under Windows).

Dan
 
J

James Kuyper

pete said:
James Kuyper wrote: ....

If the disk was the file that was associated with a stream,

I was actually thinking of a file that was one of many that were stored
on the disk, which is the case I'm more familiar with.
then no more file, implies no more association.

That's one way of looking at it; another way is to say that the
association is still there, but one of the two things being associated
is currently inaccessible (possibly forever).
N869
7.19.3 Files
[#1] A stream is associated with an external file (which may
be a physical device) by opening a file, which may involve
creating a new file.

I don't see a violation of that requirement here. The fopen() creates
the association, but the thing a stream is associated with can cease
being accessible; that's why all of the I/O functions provide mechanisms
for reporting error conditions.
 
C

CBFalconer

Dan said:
.... snip ...

As a general rule, if the standard doesn't guarantee that a certain
library function call will succeed, it can fail for unspecified
reasons. According to some committee members, even realloc(ptr, oldsize)
is allowed to fail, although it's logically a no-op.

And, on some systems, is fairly likely to. Those systems
mindlessly perform a malloc(size), check for success, and copy all
old data over. If all went well they free the old pointer. All
perfectly valid.
 
C

CBFalconer

.... snip ...

And note that there's no good reason to use fputc() rather than
putc() in this case. (In fact, there almost never a good reason
to use fputc).

n = 3;
....
putc(n++ + '0');
 
C

Chris Torek

n = 3;
....
putc(n++ + '0');

This is guaranteed to "work right" (i.e., putc '3' and leave n set
to 4, assuming n is still 3 at the previous sequence point), given
the obvious fix -- either putchar() or putc(..., stdout). The
one that might fail is:

FILE *files[N], **fpp;
...
for (fpp = &files[0]; fpp < &files[N];)
putc(expr, *fpp++);

Here the side effect on fpp might occur more than once, and/or
introduce undefined behavior.
 
P

pete

Chris said:
This is guaranteed to "work right" (i.e., putc '3' and leave n set
to 4, assuming n is still 3 at the previous sequence point), given
the obvious fix -- either putchar() or putc(..., stdout).

I don't think so.
The standard says that putc can be implemented as a macro which
evaluates it's argument more than once,
"so the argument should never be an expression with side effects."
(C89 last public draft)
 
W

Wojtek Lerch

James Kuyper said:
Wojtek Lerch wrote:
...

That's not correct - such a re-write would not handle correctly calls to
main() frome within user code (which are legal). It's more accurate to
say that the initial call to main() is essentially equivalent to

exit(main(argc,argv));

I was afraid someone was going to say that... I ignored this
possibility because it doesn't really have much to do with the topic
and makes my imaginary implementation a bit more complicated and a bit
less realistic.

But not impossible -- here's a workaround:

If a translation unit defines main(), the compiler generates a special
startup function that calls main() and then exit() exactly the way the
standard requires. When compling this special function, the compiler
decides to inline the calls to main() and exit(), and then everything
else happens the way I described before.

Does that look OK now?
 
L

lawrence.jones

In comp.std.c CBFalconer said:
putc(n++ + '0');

putc() takes *two* arguments -- the character and a stream. It's
allowed to evaluate the *stream* more than once, but not the character,
so that code is correct once the missing stream argument is added. In
my experience, side effects in the character argument are very rare
(although one can come up with plausible examples, as above) and side
effects in the stream argument are rare to the point of nonexistence.

-Larry Jones

I think we need to change the rules. -- Calvin
 
C

Chris Torek

The standard says that putc can be implemented as a macro which
evaluates it's argument more than once,
"so the argument should never be an expression with side effects."
(C89 last public draft)

Here is what a C99 draft says, which I believe is the same as
the final C89 wording:

[#2] The putc function is equivalent to fputc, except that
if it is implemented as a macro, it may evaluate stream more
than once, so the argument should never be an expression
with side effects.

In other words, putc() is explicitly allowed to evaluate its second
argument more than once, but not its first (giving rise to the
situation I described). My original stdio implementation of putc()
is rather complicated because of this -- the character to be output
gets stored in a byte attached to the underlying FILE object so
that the result can be evaluated more than once.

(POSIX threads eventually doomed the idea of inline expansion of
putc after all, so these days, the BSD/OS putc() is just an ordinary
function anyway.)
 
P

Peter Nilsson

CBFalconer said:
And, on some systems, is fairly likely to.

What, fail? [More likely, obviously, but fairly likely?!]
Those systems
mindlessly perform a malloc(size), check for success, and copy all
old data over. If all went well they free the old pointer. All
perfectly valid.

I've read articles that this behaviour is chosen because of a literal
interpretation of the standard: "...returns a pointer to a _new_
object". So, it's over-pedantism, rather than mindlessness. ;)
 
P

pete

Chris said:
The standard says that putc can be implemented as a macro which
evaluates it's argument more than once,
"so the argument should never be an expression with side effects."
(C89 last public draft)

Here is what a C99 draft says, which I believe is the same as
the final C89 wording:

[#2] The putc function is equivalent to fputc, except that
if it is implemented as a macro, it may evaluate stream more
than once, so the argument should never be an expression
with side effects.

In other words, putc() is explicitly allowed to evaluate its second
argument more than once, but not its first (giving rise to the
situation I described). My original stdio implementation of putc()
is rather complicated because of this -- the character to be output
gets stored in a byte attached to the underlying FILE object so
that the result can be evaluated more than once.

(POSIX threads eventually doomed the idea of inline expansion of
putc after all, so these days, the BSD/OS putc() is just an ordinary
function anyway.)

A closer reading of my C89 last draft, shows the same thing.
Thank you.
 
C

CBFalconer

Chris said:
n = 3;
....
putc(n++ + '0');

This is guaranteed to "work right" (i.e., putc '3' and leave n set
to 4, assuming n is still 3 at the previous sequence point), given
the obvious fix -- either putchar() or putc(..., stdout). The
one that might fail is:

FILE *files[N], **fpp;
...
for (fpp = &files[0]; fpp < &files[N];)
putc(expr, *fpp++);

Here the side effect on fpp might occur more than once, and/or
introduce undefined behavior.

What guarantees that one macro parameter is evaluated only once,
and not the other? I know there are some special cases in the
standard, but on principle I wouldn't rely on it. I thought that
in general standard functions may be implemented as macros,
provided they never evaluate arguments more than once, but that
putc (and getc) were exceptions. Thus fputc and fgetc must be
available.
 
D

Dan Pop

In said:
[email protected] (Dan Pop) said:
In said:
(e-mail address removed) (Dan Pop) writes:
[...]
After I posted this, I realized that a swap file or partition (if
there is one) can also be treated as a file, so modifying a variable
could conceivably "modify a file".

A swap file or partition is not a file, in the sense of the C standard.

A swap file is a file if it has a name that can be used for a
successful call to fopen(). (Linux even has a "swapon" command that
causes an existing file to be used as a swap file.)

Once you activate swapping on a certain file, this file no longer has
the semantics of standard C file, even if you can access it from a C
program.

Chapter and verse, please. C99 7.9.13 describes (in deliberately
vague terms) what a "file" is. It's clear that it can be a disk file
or a device (including a terminal). I see nothing in the description,
or anywhere else in the standard, that would exclude, for example, a
disk file or device to which the Linux "swapon" command has been
applied.

Data read in from a text stream
will necessarily compare equal to the data that were earlier
^^^^^^^^^^^
written out to that stream only if: the data consist only of
printing characters and the control characters horizontal tab
and new-line; no new-line character is immediately preceded by
space characters; and the last character is a new-line character.

Connect the text stream to an active swap file and the above guarantee
is no longer satisfied by the implementation.

Dan
 
J

James Kuyper

CBFalconer said:
Chris Torek wrote: ....
FILE *files[N], **fpp;
...
for (fpp = &files[0]; fpp < &files[N];)
putc(expr, *fpp++);

Here the side effect on fpp might occur more than once, and/or
introduce undefined behavior.

What guarantees that one macro parameter is evaluated only once,
and not the other? I know there are some special cases in the

7.19.7.8p2: "The putc function is equivalent to fputc, except that if it
is implemented as a macro, it may evaluate stream more than once, so
that argument should never be an expression with side effects."

An implementation of putc() as a macro which evaluated it's first
argument more than once would inequivalent to fputc(), in a way other
than the only allowed exception to that equivalence.
 

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
473,768
Messages
2,569,574
Members
45,048
Latest member
verona

Latest Threads

Top