It's in 7.20.4.3p3, immediately following the p2 you read:
*First*, all functions registered by the atexit function are called,
[...]
I took that sequence as specifying the relative order of the events it
lists - which are the the things required of "normal termination" -
rather than an exhaustive list of what happens in exit().
I could say the same about printf, meaning
#include <stdio.h>
int main(void) {
int i = 0;
printf("%d\n", i);
printf("%d\n", i);
return 0;
}
might print 0 and 1, because the description of printf doesn't say i
won't be modified. Clearly, this doesn't work. The description of a
function is a complete description. The description of exit describes all
the steps performed in normal program termination. If exit longjmps to
outside of main, it must be one of the three steps, and given only these
three steps, it's not possible for it to occur before the atexit
registrations are called.
Or, for something more explicit but non-normative, see the footnote
attached to 5.1.2.2.3:
In accordance with 6.2.4, the lifetimes of objects with automatic
storage duration declared in main will have ended in the former case
[returning from main], even where they would not have in the latter
[calling exit].
That doesn't seem unambiguous to me: "even where" implies that it is not
necessarily the case.
Indeed:
#include <stdlib.h>
int main(void) {
{ int i; }
/* i is declared in main, but no longer live when exit is called */
exit(0);
}
And I see that the (equally non-normative)
Rationale says (7.20.4.3):
Aside from calls explicitly coded by a programmer, exit is invoked on
return from main. Thus in at least this case, the body of exit cannot
assume the existence of any objects with automatic storage duration
except those declared in exit.
"At least" again suggests that main() may have returned when exit() is
called explicitly.
I don't believe there's any prohibition against a function registered
with atexit calling exit again (though it should not do so
unconditionally, for obvious reasons, and preferably should not do so at
all). Additionally, it may refer to implementation extensions to call
code after returning from main without help of atexit.