Topic: post-main() access violation due to static function-scoped variables constructed during onexit processing (e.g. global object destruction)
Author: Pierre Baillargeon <pierre@jazzmail.com>
Date: 1997/12/15 Raw View
Steve Clamage wrote:[...]
> In the case in question, a function F was registered with atexit
> before its local static object O could possibly be initialized. On
> that basis, it must not be called by exit processing until after O's
> destructor completes. But the destructor cannot be run until after F
> is called, since otherwise the object could not be constructed in the
> first place.
>
> So it seems to me that if the program is valid, the standard requires
> the implementation to perform a logical impossibility. Each of two
> things must be completed before the other begins.
>
> On the basis that the program cannot be interpreted in a manner
> consistent with the draft standard, I would view the program as
> invalid.
Since static variable hidden in classes cannot always be known to the
user of the class, making such programs invalid could cause hard-to-
find problems. For example, iostreams-like classes that need to be
initialized before everything else could not be implemented correctly.
One solution is to define what happens when atexit is called within
atexit. A solution would be to do the following.
1. When the atexit processing begins, copy the stack-like entity that
holds the registered functions into another area, which is then used
by the atexit processing to decide which function to run.
2. During that processing, any call to atexit registers the function in
the normal atexit stack-like entity, not affecting the order of the
copied one.
3. When the copied one is empty (i.e. all functions that were registered
before the processing of step 1 began have been called), check if the
normal atexit stack-like entity is empty. If not empty then restart at
step 1.
That solution keeps the program that are currently well-defined unchanged.
It also defines the behavior of undefined programs. And it is, I think,
the logical behavior if we are to avoid the impossibility described above
(having two things that must be run before each other). Finally, it is not
hard to implement.Another solution (beside a crash) is to have all functions
registered after atexit be run in the order of registration. Most ugly.
After all, even if the behavior is left undefined, the compilers will have
to decide on a particular behavior, and a crash is the worst possible one.
I hope the will to leave it undefined is not simply the desire not to
affect the just-released standard.
---
[ comp.std.c++ is moderated. To submit articles: Try just posting with your
newsreader. If that fails, use mailto:std-c++@ncar.ucar.edu
comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
Comments? mailto:std-c++-request@ncar.ucar.edu
]
Author: "Bill Wade" <bill.wade@stoner.com>
Date: 1997/12/17 Raw View
[ what happens when atexit is called, or a static object is constructed,
while an exit is in progress ]
I think it is pretty clear that this is undefined. To follow the standard,
an implementation would have to destruct objects before they were
constructed, which is not likely to be acceptable :-).
I saw three suggestions on what "should" be done.
1) It should be undefined.
2) It should behave like a single stack:
{
while(!exitStack.empty())
{
func = exitStack.top();
exitStack.pop();
func();
}
}
where func() may push additional items onto the stack.
3) One stack at a time should be processed:
{
while(!exitStack.empty())
{
ExitStack temp; // Construct an empty stack.
swap(temp,exitStack);
while(! temp.empty())
{
func = temp.top();
func();
temp.pop();
}
}
}
It seems that (2) is easy to understand (it is the same thing that happens
when auto variables
are created). Instead of "static variables are destructed in reverse order
of completion of construction" the statement becomes "static variables with
overlapping lifetimes are destructed ...".
It also seems that (2) is easy to implement (unless the implementer doesn't
write atexit(), merely uses an existing C atexit, or similar facility).
Maybe this should make it onto the list of things to change in five years.
Better yet, maybe somebody can con (convince that is :-) C9X to take care
of it now.
---
[ comp.std.c++ is moderated. To submit articles: Try just posting with your
newsreader. If that fails, use mailto:std-c++@ncar.ucar.edu
comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
Comments? mailto:std-c++-request@ncar.ucar.edu
]
Author: stephen.clamage_nospam@Eng.Sun.COM (Steve Clamage)
Date: 1997/12/12 Raw View
On 05 Dec 97 05:30:24 GMT, "McInerny, Michael"
<mcinerny@claritech.com> wrote:
[ Oversimplified statement of the problem: During program exit, a
function registered with atexit() caused an additional function to be
registered. That caused a crash. Does that represent an implementation
bug? ]
> ... Well, we
>had a file-scoped static class instance (file "global") whose
>destructor was called during doexit (the C++ compiler apparently uses
>atexit to register dtor callbacks on statics). This static class was
>the one-and-only instance ever allocated during the course of the
>execution of the program, so its destructor was called only once,
>during doexit processing. The destructor, however, had a function
>scoped static class instance [see where we're going?], and this being
>the first invocation of the function, the C++ code registered its
>destructor via atexit. Well, we got unlucky (case 2, above), and
>sometime later the doexit processing tripped over a bad function
>pointer, causing an alert post-exit() in our application.
First, let's suppose the programmer actually wrote code like this:
#include <stdlib.h>
void f1() { }
void f2() { atexit(f1); }
int main()
{
atexit(f2); // the only use of f2
}
At program exit, f2 gets called due to its registration by main.
Running f2 causes f1 to be newly registered during the exit
processing. Is this a valid program?
Interestingly, neither the C standard, nor the C++ draft standard nor
the forthcoming C9X Committee Draft says directly whether you can
register a function with atexit during exit processing.
They all say that functions are run in reverse order of their
registration. Since f1 is registered last, it ought to be run first,
but by the time it is registered, it is too late to be first. I think
you could look at this situation as violating an implicit precondition
of atexit -- that exit hasn't started to run yet. But the C and C++
standards don't really say one way or the other.
Unless someone finds a requirement that I missed, I think that
question is open. That isn't the question that was actually asked, but
I wanted to ease into the problem with a simpler case.
The actual problem was this: A function F registered with atexit had a
local static variable, and the function was called for the first time
during exit processing. A local static object is initialized the first
time control flow passes through its definition. Thus, the static
object could not be constructed until after exit processing began, and
had to be destroyed during exit processing. The C++ implementation
handled destruction of static objects by registering their destructors
with atexit. That led to a situation similar in effect to my example
above. Is that implemenation valid?
Section 18.3 "Start and termination" of the C++ draft standard says
that static objects are destroyed in the same exit processing that
runs the functions registered with atexit. In particular, it says that
if a function F is registered with atexit before a static object O is
initialized, F will not be called until after O's destructor
completes. (That is, functions registered with atexit are called in
reverse order of registration, and constructing a static object has an
effect like that of registering its destructor with atexit.) (BTW,
this detail is a recent addition to the draft. Previously, the draft
didn't say what the order was.)
In the case in question, a function F was registered with atexit
before its local static object O could possibly be initialized. On
that basis, it must not be called by exit processing until after O's
destructor completes. But the destructor cannot be run until after F
is called, since otherwise the object could not be constructed in the
first place.
So it seems to me that if the program is valid, the standard requires
the implementation to perform a logical impossibility. Each of two
things must be completed before the other begins.
On the basis that the program cannot be interpreted in a manner
consistent with the draft standard, I would view the program as
invalid.
But here again, the standard ought to say what happens in this case.
(My vote would be for undefined behavior.)
---
Steve Clamage, stephen.clamage_nospam@eng.sun.com
( Note: remove "_nospam" when replying )
---
[ comp.std.c++ is moderated. To submit articles: Try just posting with your
newsreader. If that fails, use mailto:std-c++@ncar.ucar.edu
comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
Comments? mailto:std-c++-request@ncar.ucar.edu
]
Author: stephen.clamage_nospam@eng.sun.com (Steve Clamage)
Date: 1997/12/13 Raw View
On 05 Dec 97 05:30:24 GMT, "McInerny, Michael"
<mcinerny@claritech.com> wrote:
[ Oversimplified statement of the problem: During program exit, a
function registered with atexit() caused an additional function to be
registered. That caused a crash. Does that represent an implementation
bug? ]
> ... Well, we
>had a file-scoped static class instance (file "global") whose
>destructor was called during doexit (the C++ compiler apparently uses
>atexit to register dtor callbacks on statics). This static class was
>the one-and-only instance ever allocated during the course of the
>execution of the program, so its destructor was called only once,
>during doexit processing. The destructor, however, had a function
>scoped static class instance [see where we're going?], and this being
>the first invocation of the function, the C++ code registered its
>destructor via atexit. Well, we got unlucky (case 2, above), and
>sometime later the doexit processing tripped over a bad function
>pointer, causing an alert post-exit() in our application.
First, let's suppose the programmer actually wrote code like this:
#include <stdlib.h>
void f1() { }
void f2() { atexit(f1); }
int main()
{
atexit(f2); // the only use of f2
}
At program exit, f2 gets called due to its registration by main.
Running f2 causes f1 to be newly registered during the exit
processing. Is this a valid program?
Interestingly, neither the C standard, nor the C++ draft standard nor
the forthcoming C9X Committee Draft says directly whether you can
register a function with atexit during exit processing.
They all say that functions are run in reverse order of their
registration. Since f1 is registered last, it ought to be run first,
but by the time it is registered, it is too late to be first. I think
you could look at this situation as violating an implicit precondition
of atexit -- that exit hasn't started to run yet. But the C and C++
standards don't really say one way or the other.
Unless someone finds a requirement that I missed, I think that
question is open. That isn't the question that was actually asked, but
I wanted to ease into the problem with a simpler case.
The actual problem was this: A function F registered with atexit had a
local static variable, and the function was called for the first time
during exit processing. A local static object is initialized the first
time control flow passes through its definition. Thus, the static
object could not be constructed until after exit processing began, and
had to be destroyed during exit processing. The C++ implementation
handled destruction of static objects by registering their destructors
with atexit. That led to a situation similar in effect to my example
above. Is that implemenation valid?
Section 18.3 "Start and termination" of the C++ draft standard says
that static objects are destroyed in the same exit processing that
runs the functions registered with atexit. In particular, it says that
if a function F is registered with atexit before a static object O is
initialized, F will not be called until after O's destructor
completes. (That is, functions registered with atexit are called in
reverse order of registration, and constructing a static object has an
effect like that of registering its destructor with atexit.) (BTW,
this detail is a recent addition to the draft. Previously, the draft
didn't say what the order was.)
In the case in question, a function F was registered with atexit
before its local static object O could possibly be initialized. On
that basis, it must not be called by exit processing until after O's
destructor completes. But the destructor cannot be run until after F
is called, since otherwise the object could not be constructed in the
first place.
So it seems to me that if the program is valid, the standard requires
the implementation to perform a logical impossibility. Each of two
things must be completed before the other begins.
On the basis that the program cannot be interpreted in a manner
consistent with the draft standard, I would view the program as
invalid.
But here again, the standard ought to say what happens in this case.
(My vote would be for undefined behavior.)
---
Steve Clamage, stephen.clamage_nospam@eng.sun.com
( Note: remove "_nospam" when replying )
---
[ comp.std.c++ is moderated. To submit articles: Try just posting with your
newsreader. If that fails, use mailto:std-c++@ncar.ucar.edu
comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
Comments? mailto:std-c++-request@ncar.ucar.edu
]
Author: "Paul D. DeRocco" <pderocco@ix.netcom.com>
Date: 1997/12/13 Raw View
Steve Clamage wrote:
>
> First, let's suppose the programmer actually wrote code like this:
>
> #include <stdlib.h>
> void f1() { }
> void f2() { atexit(f1); }
> int main()
> {
> atexit(f2); // the only use of f2
> }
>
> At program exit, f2 gets called due to its registration by main.
> Running f2 causes f1 to be newly registered during the exit
> processing. Is this a valid program?
>
> Interestingly, neither the C standard, nor the C++ draft standard nor
> the forthcoming C9X Committee Draft says directly whether you can
> register a function with atexit during exit processing.
>
> They all say that functions are run in reverse order of their
> registration. Since f1 is registered last, it ought to be run first,
> but by the time it is registered, it is too late to be first. I think
> you could look at this situation as violating an implicit precondition
> of atexit -- that exit hasn't started to run yet. But the C and C++
> standards don't really say one way or the other.
A reasonable implementation for all post-main processing would be a stack
implemented as a singly-linked list of nodes containing pointers to functions
to be executed, rooted in some global location. Calling atexit would simply
push something onto the stack by malloc-ing a pair of pointers. After main
returns, each item would be popped off the list, and the function called, in a
loop. In this case, the above code would be well-defined: when f2() registered
f1(), that would merely make f1() next in line to be called once f2() returned.
--
Ciao,
Paul
---
[ comp.std.c++ is moderated. To submit articles: Try just posting with your
newsreader. If that fails, use mailto:std-c++@ncar.ucar.edu
comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
Comments? mailto:std-c++-request@ncar.ucar.edu
]
Author: "McInerny, Michael" <mcinerny@claritech.com>
Date: 1997/12/05 Raw View
I've reported this bug to MS, but haven't heard a response and saw
nothing about it in the VisualStudio SP3 release notes, so I thought I
would send it along to fellow C++ programmers who might be scratching
their heads at a curious MS bug....
[Moderator's note: I've accepted this as relevant to comp.std.c++
because of the implicit question "is this really a bug, or does
the standard say the behaviour is undefined?". -mod (fjh).]
-------------------------------------
The doexit processing loop (in CRT0DAT.C) uses a pointer (_PVFV
*pfend) to the __onexit{begin,end} array of function callbacks. The
problem is that any modification of this array during the function
callback (e.g. calling atexit or _onexit) will cause two problems:1-
if you're lucky, the function will be added to the end of the array,
and will simply never be called;2- if you're unlucky, and the array is
full, the array will be realloc'ated. Problem (2) is fatal, since now
the pfend pointer probably no longer points to an address between
__onexitbegin and __onexitend, and the loop test (--pfend >=
__onexitbegin) will allow pfend to traipse all over memory. This is
really bad because pfend will be dereferenced and invoked as a
function, leading to an illegal address trap (or worse -- it's a
random pointer). The fix seems simple: don't use a temporary pointer,
but rather decrement __onexitend directly towards __onexitbegin. This
maintains the invariant that __onexitend points to the end of the
function array, allowing pushes (via atexit/onexit) to be correctly
registered and called (problem 1), and avoids wild pointers into
abandoned-via-realloc memory (problem 2). Incidentally, we tripped over
this problem because of the C++ code generator: static class
instances are allocated "lazily", as they are encountered. Well, we
had a file-scoped static class instance (file "global") whose
destructor was called during doexit (the C++ compiler apparently uses
atexit to register dtor callbacks on statics). This static class was
the one-and-only instance ever allocated during the course of the
execution of the program, so its destructor was called only once,
during doexit processing. The destructor, however, had a function
scoped static class instance [see where we're going?], and this being
the first invocation of the function, the C++ code registered its
destructor via atexit. Well, we got unlucky (case 2, above), and
sometime later the doexit processing tripped over a bad function
pointer (0xfdfdfdfd), causing an alert post-exit() in our
application. We commented out the static in this function (and the
functions the destructor called), and the problem went away. The
static was actually inserted by some instrumentation code wrapped up
in a macro, FYI. [Not an uncommon thing to do.] I believe this problem
has existed since 4.2. I haven't bothered to look at the DLL
doexit/_onexit processing, but I suspect it might be problematic
too.
Thanks for your attention in this matter.
Sincerely,
-Michael McInerny
Director of Technology & Engineering
CLARITECH Corporation
mcinerny@claritech.com
---
[ comp.std.c++ is moderated. To submit articles: try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ FAQ: http://reality.sgi.com/employees/austern_mti/std-c++/faq.html ]
[ Policy: http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu ]