exit, atexit and scope

L

Laurent Deniau

I would like to know if the use of the pointer ref in the function
cleanup() below is valid or if something in the norm prevents this
kind of cross-reference during exit(). I haven't seen anything in the
norm against this, I mean an as-if rule saying "atexit registered
functions are executed as-if they were called from main", making val
out of scope at this point.

a+, ld.

#include <stdio.h>
#include <stdlib.h>

int *ref;

void cleanup(void) {
if (ref) fprintf(stderr, "val = %d\n", *ref);
}

void test(void) {
int val = 12;
ref = &val;
exit(EXIT_FAILURE);
}

int main(void) {
atexit(cleanup);
test();
return 0;
}
 
B

Ben Bacarisse

Laurent Deniau said:
I would like to know if the use of the pointer ref in the function
cleanup() below is valid or if something in the norm prevents this
kind of cross-reference during exit(). I haven't seen anything in the
norm against this, I mean an as-if rule saying "atexit registered
functions are executed as-if they were called from main", making val
out of scope at this point.

You don't mean scope. You are talking about the lifetime of the
variable 'val'. The name 'val' is out of scope during the whole time
exit is doing its job, but the variable, now pointed to by 'ref', still
exists. I think it is safe.
 
R

Richard

Ben Bacarisse said:
You don't mean scope. You are talking about the lifetime of the
variable 'val'. The name 'val' is out of scope during the whole time
exit is doing its job, but the variable, now pointed to by 'ref', still
exists. I think it is safe.

I dont think it is safe.

test() runs but "int val" is effectively "gone" when the function exits.

At this point cleanup is called.

Surely for this to be OK val would need to be a static?

But, I welcome correction.
 
P

Philip Potter

Laurent said:
I would like to know if the use of the pointer ref in the function
cleanup() below is valid or if something in the norm prevents this
kind of cross-reference during exit(). I haven't seen anything in the
norm against this, I mean an as-if rule saying "atexit registered
functions are executed as-if they were called from main", making val
out of scope at this point.

a+, ld.

#include <stdio.h>
#include <stdlib.h>

int *ref;

void cleanup(void) {
if (ref) fprintf(stderr, "val = %d\n", *ref);

This test doesn't make sense if it is at all possible that ref is
uninitialised. In this program, it's fine, but consider initialising ref
to NULL at the start of main().
 
B

Ben Bacarisse

Richard said:
I dont think it is safe.


test() runs but "int val" is effectively "gone" when the function
exits.

but exit is called before test returns (in this context using the word
'exit' for a function return in going to be a problem!). From my
reading of the standard, the actions of exit -- i.e. the calling of
all registered atexit functions -- take place like a normal function
call. test() would return only after exit is done. I say "would"
because this can happen only when exit returns to its caller -- and
that does not happen (7.20.4.3 p6).
At this point cleanup is called.

No. You missed the call of exit in test().
 
B

Ben Bacarisse

Richard said:
Quite correct!

Is it "defined" that val is still there in this case?

I can't see why not. If exit() were my_func() and my_func called the
same functions that exit() does it certainly would be. I can't see any
special case text that makes what exit() does special enough to
invalidate this argument.
 
R

Richard

Ben Bacarisse said:
I can't see why not. If exit() were my_func() and my_func called the
same functions that exit() does it certainly would be. I can't see any
special case text that makes what exit() does special enough to
invalidate this argument.

Putting it that way I agree. I kind of had it in my mind that calling
exit() was a bit more severe than my_function(). But thinking about it
atoexit would be pretty useless if everything at global scope at least
wasn't available, so why should the stack(!?) for test() have been
folded. Interesting question though.
 
F

Flash Gordon

Philip Potter wrote, On 15/11/07 15:06:
This test doesn't make sense if it is at all possible that ref is
uninitialised. In this program, it's fine, but consider initialising ref
to NULL at the start of main().

<snip>

Not required. Any variable defined at file scope or defined as static in
block scope is initialised to an appropriate zero if no initialisation
is provided, so ref is initialised to a null pointer.
 
E

Eric Sosman

Ben Bacarisse wrote On 11/15/07 11:36,:
I can't see why not. If exit() were my_func() and my_func called the
same functions that exit() does it certainly would be. I can't see any
special case text that makes what exit() does special enough to
invalidate this argument.

A possible implementation of exit():

#include <stdlib.h>
#include <setjmp.h>
#include "_implementation_magic.h"
void exit(int code) {
_exit_status = code;
longjmp (_pre_main_jmpbuf, 1);
}

.... and Boom! all the program's `auto' variables vanish.
Any atexit-registered functions had better not try to
use them ...

I don't know off-hand whether any implementations'
exit() functions work this way, but I don't see anything
in the Standard that would forbid it. So I, for one,
will not make use of possibly-deceased `auto' variables
in my atexit() functions, and I'd urge the same course
on others.
 
B

Ben Bacarisse

Eric Sosman said:
Ben Bacarisse wrote On 11/15/07 11:36,:

A possible implementation of exit():

#include <stdlib.h>
#include <setjmp.h>
#include "_implementation_magic.h"
void exit(int code) {
_exit_status = code;
longjmp (_pre_main_jmpbuf, 1);
}

... and Boom! all the program's `auto' variables vanish.
Any atexit-registered functions had better not try to
use them ...

Hmmm... OK. But before I fold:

My argument was based on the description of exit which has the rather
suggestive phrase: "First, all functions registered by the atexit
function are called". It can't see how it can do anything of
significance before it does what it must do "first". Is that reading
too much into the operational description of its actions?
I don't know off-hand whether any implementations'
exit() functions work this way, but I don't see anything
in the Standard that would forbid it. So I, for one,
will not make use of possibly-deceased `auto' variables
in my atexit() functions, and I'd urge the same course
on others.

I certainly would not promote the style! It is obviously very fragile
even if I am right (and I am not so sure any more).
 
E

Eric Sosman

Ben Bacarisse wrote On 11/15/07 17:43,:
Hmmm... OK. But before I fold:

My argument was based on the description of exit which has the rather
suggestive phrase: "First, all functions registered by the atexit
function are called". It can't see how it can do anything of
significance before it does what it must do "first". Is that reading
too much into the operational description of its actions?

I think so (obviously ...). If "first" were taken
absolutely literally, as in "The very first executable
statement in exit() must be a call to the most recently
registered atexit() function," that would appear to
outlaw even an `if' to discover if any functions were
registered!

More realistically (perhaps) if exit() looked like

void exit(int status) {
_call_atexit_functions();
fflush(NULL);
_close_open_streams();
_remove_tempfiles();
_really_exit(status);
}

.... then the "first" thing exit() calls is not one of
the exit handlers, but a helper function that in turn
calls those handlers. If you think this implementation
is conforming, then a longjmp() to somewhere else that
does all the same things ought to be conforming, too.
 
D

Dik T. Winter

> Ben Bacarisse wrote On 11/15/07 11:36,: ....
>
> A possible implementation of exit():
>
> #include <stdlib.h>
> #include <setjmp.h>
> #include "_implementation_magic.h"
> void exit(int code) {
> _exit_status = code;
> longjmp (_pre_main_jmpbuf, 1);
> }
>
> ... and Boom! all the program's `auto' variables vanish.
> Any atexit-registered functions had better not try to
> use them ...

7.20.4.3.2: First, all functions registered by the atexit function are
called, in reverse order of their registration.

The above implemetation does not conform to this.
 
B

Ben Bacarisse

Eric Sosman said:
Ben Bacarisse wrote On 11/15/07 17:43,:

I think so (obviously ...). If "first" were taken
absolutely literally, as in "The very first executable
statement in exit() must be a call to the most recently
registered atexit() function," that would appear to
outlaw even an `if' to discover if any functions were
registered!

Come now, I said "do anything of significance"!
More realistically (perhaps) if exit() looked like

void exit(int status) {
_call_atexit_functions();
fflush(NULL);
_close_open_streams();
_remove_tempfiles();
_really_exit(status);
}

... then the "first" thing exit() calls is not one of
the exit handlers, but a helper function that in turn
calls those handlers. If you think this implementation
is conforming, then a longjmp() to somewhere else that
does all the same things ought to be conforming, too.

I think if the atexit functions are permitted to be run after
destroying all local variables it deserves a mention, so I am arguing
partly from the significance of an omission.

A final shot: C99 has VLAs, and I would have thought it at least
possible that "rescuing" their contents in an atexit function is
supposed to be allowed. Not a style I'd advocate, but pointing to a
big VLA just before calling a function that might call exit (maybe a
really badly written library function) is a use-case that needs to be
clarified.
 
E

Eric Sosman

Dik said:
7.20.4.3.2: First, all functions registered by the atexit function are
called, in reverse order of their registration.

The above implemetation does not conform to this.

Not sure what your objection is. It might be the meaning
of "first," as in Ben Bacarisse's discussion, in which case I
don't think c.l.c. is likely to arrive at any definitive answer.
But if (and I'm only guessing) you imagine that this exit() would
somehow fail to run the atexit() functions, my answer is that
they could be "called"[*] from the pre-main() code to which the
longjmp() returns:

switch (setjmp(_pre_main_jmpbuf)) {
case 0: /* first attempt */
_exit_status = main(argc, argv);
/* drop through */
case 1: /* exit() called */
_run_atexit_functions();
fflush(NULL);
_close_all_streams();
_remove_tempfiles();
/* drop through */
case 2: /* _Exit() called */
_really_exit(_exit_status);
}

[*] Please, no quibbles about whether "called" can be used
in connection with code that might not be written in C.
 
J

Jack Klein

Ben Bacarisse wrote On 11/15/07 11:36,:

A possible implementation of exit():

#include <stdlib.h>
#include <setjmp.h>
#include "_implementation_magic.h"
void exit(int code) {
_exit_status = code;
longjmp (_pre_main_jmpbuf, 1);
}

... and Boom! all the program's `auto' variables vanish.
Any atexit-registered functions had better not try to
use them ...

Such an implementation would not be standard conforming. Footnote 10
attached to 5.1.2.3 specifically points out that automatic variables
in main() still exist when if main() calls exit(). It refers to
6.2.4, where paragraph 6 specifically states about automatic objects,
"For such an object that does have a variable length array type, its
lifetime extends from the declaration of the object until execution of
the program leaves the scope of the declaration."

A call to a function, including exit(), does not leave the scope of
objects defined in the calling function.

--
Jack Klein
Home: http://JK-Technology.Com
FAQs for
comp.lang.c http://c-faq.com/
comp.lang.c++ http://www.parashift.com/c++-faq-lite/
alt.comp.lang.learn.c-c++
http://www.club.cc.cmu.edu/~ajo/docs/FAQ-acllc.html
 
L

Laurent Deniau

7.20.4.3.2: First, all functions registered by the atexit function are
called, in reverse order of their registration.
The above implemetation does not conform to this.

Not sure what your objection is. It might be the meaning
of "first," as in Ben Bacarisse's discussion, in which case I
don't think c.l.c. is likely to arrive at any definitive answer.
But if (and I'm only guessing) you imagine that this exit() would
somehow fail to run the atexit() functions, my answer is that
they could be "called"[*] from the pre-main() code to which the
longjmp() returns:

switch (setjmp(_pre_main_jmpbuf)) {
case 0: /* first attempt */
_exit_status = main(argc, argv);
/* drop through */
case 1: /* exit() called */
_run_atexit_functions();
fflush(NULL);
_close_all_streams();
_remove_tempfiles();
/* drop through */
case 2: /* _Exit() called */
_really_exit(_exit_status);
}

This is exactly what I had in mind with my question. Unfortunately,
the norm seems to says nothing explicit about this possible
implementation and it seems to be very hard to detect automatically
this behavior at runtime. Still, this implementation can be considered
as an awkward case in the absence of explicit information, therefore I
guess that it is not what the norm means/expects. No information on a
point should mean "follow common sens", not "as tricky as possible".

Nevertheless, adding a word about this point in the next release of
the norm would be nice, as far as it doesn't break any existing
implementation (e.g. one doing something like your code).

a+, ld.
 
L

Laurent Deniau

Hmmm... OK. But before I fold:

My argument was based on the description of exit which has the rather
suggestive phrase: "First, all functions registered by the atexit
function are called". It can't see how it can do anything of
significance before it does what it must do "first". Is that reading
too much into the operational description of its actions?


I certainly would not promote the style! It is obviously very fragile
even if I am right (and I am not so sure any more).

I am not talking to promote this style, but there is cases where this
is required like in "stack unwinding a-la-C++". You have a global
pointer pointing to a linked list of "stacked" pointers to automatic
variables holding critical resources and a proper implementation
should call their destructor before exit()ing. I am open to any
alternative, but the problem is not obvious.

In fact, either C assumes that files are the only critical resource
which need to be cleanup before exit()ing, either the implementation
exibited by E. Sosman is not allowed. I cannot believe that the former
is the intend of the norm.

a+, ld.
 
E

Eric Sosman

Jack said:
[... exit() might longjmp() to the caller of main() ...]

Such an implementation would not be standard conforming. Footnote 10
attached to 5.1.2.3 specifically points out that automatic variables
in main() still exist when if main() calls exit(). It refers to
6.2.4, where paragraph 6 specifically states about automatic objects,
"For such an object that does have a variable length array type, its
lifetime extends from the declaration of the object until execution of
the program leaves the scope of the declaration."

Thanks for pointing out the footnote (on 5.1.2.2.3, by the way).
Now it's *my* turn to cavil like a Philadelphia lawyer, taking
cowardly refuge behind the "footnotes are non-normative" parapet.
An exit() that obeys the intent expressed in the footnote could
not operate as I suggested, and if it follows that intent then
Laurent Deniau's original code is fine. Scary, but fine.
A call to a function, including exit(), does not leave the scope of
objects defined in the calling function.

What about a call to the longjmp() function itself?
 
D

Dik T. Winter

> Dik T. Winter wrote: ....
> >
> > 7.20.4.3.2: First, all functions registered by the atexit function are
> > called, in reverse order of their registration.
> >
> > The above implemetation does not conform to this.
>
> Not sure what your objection is. It might be the meaning
> of "first," as in Ben Bacarisse's discussion, in which case I
> don't think c.l.c. is likely to arrive at any definitive answer.
> But if (and I'm only guessing) you imagine that this exit() would
> somehow fail to run the atexit() functions, my answer is that
> they could be "called"[*] from the pre-main() code to which the
> longjmp() returns:

No. My objection is that the discussion is about "exit" as a function.
The operations it has to do have to be done in the context where the
function is called. It is only at the end where it is stated the the
function goes out of context. Or would you contend that each function
is allowed to first go out of context, only to restore the context
when returning? In that case passing pointers to local variables to
functions would be bad practice.
 

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

Forum statistics

Threads
473,754
Messages
2,569,521
Members
44,995
Latest member
PinupduzSap

Latest Threads

Top