Here’s a puzzle that should highlight a couple of interesting features in C++:
What is the output of the following program?
#include <iostream>; using namespace std; int main(); void term() { cout << "term()" << endl; main(); } struct Positive { Positive(int i) { set_terminate(term); cout << "Positive::Positive(" << i << ")" << endl; if (i <= 0) throw main; } }; Positive n(-1); int main() { cout << "main()" << endl; Positive p(1); }
If you just want the answer, you will find it at the bottom of this post. But first, I will go through the why.
Lines 1-2 are uninteresting. Lines 4-18 contain all the magic, but I’ll come back to those, I want to go through the program in chronological order as it is being executed. The entry point for this program is not main()
, but rather the definition of Positive n(-1)
. This variable is global, and so will be initialized before main()
is called. To initialize it, its constructor is called with i = -1
, and so the real action starts on line 13.
On line 13, I use set_terminate()
to set a termination handler. The termination handler will be called if a thrown exception is not handled. Otherwise, this statement has no visible immediate effect. We then encounter the first printout of this program, on line 14:
Positive::Positive(-1)
Our Positive class is however designed to only handle positive numbers, and throws on line 16. It is set to throw main
, but this is really just a decoy. In C++, you can throw whatever you want, I happen to throw a function pointer to main()
. In particular, this does not mean that main()
is called.
The exception thrown on line 16 is never handled, and so the builtin terminate()
calls the termination handler set_terminate()
, which is defined on lines 6-9. Now we encounter our second printout:
term()
This is a straightforward cout
on line 7. Then, we manually call main()
. main
is just a normal function, and even though it is special in that C++ will call it for you at startup, you are free to call it manually whenever you want. This is the third printout:
main()
main()
then attempts to instantiate the local Positive
object p(1)
. This calls the Positive
constructor, which prints out
Positive::Positive(1)
This is a valid positive number, so no more exceptions are thrown. Control is returned to main()
, which returns control to term()
, which, being a termination handler, is not allowed to return, but still attempts to do so. I cannot find anything in the standard about what exactly is supposed to happen in this case, so I guess this is undefined behaviour. No matter what, returning doesn’t make any sense, as there is nowhere to return to. What happens on my system (gcc@linux) is program abortion with SIGABRT. What happens on yours?
Finally, the complete output of the program
$ ./outsidemain Positive::Positive(-1) term() main() Positive::Positive(1) Aborted
This is probably not the most useful post I have written, but it highlights a few interesting points, and making a puzzle is always fun.
I think there are a few too many red herrings in this post :)
The main function is not quite like any function, in that it cannot be _used_ in a program (for the ODR definition of “use”). Thus, you cannot call main directly, nor take its address (which happens when the function type is implicitly converted to a function pointer type in the throw-expression.
So the answer is that the program is ill-formed :)
In GCC, you actually can. So I guess it is ill-formed and implementation dependent! ;)
Hey there,
Very useful post! I would like to invite you to visit my blog as well, and read my latest post about sequence points in C and C++.
http://blog.panqnik.pl/dev/sequence-points-in-c-cpp/
Best regards,
panqnik