7

I'm working with a C/Fortran library from C++ and the library calls exit(). I would like it to throw an exception so that the destructors in my C++ program will be called. I have been able to create my one definition of exit that throws an exception, but then terminate is still called. Is there to prevent terminate from being called and allow the normal exception handling to happen?

UPDATE: In the comments it was pointed out that this works on x64 but fails on x86, so the main question is "is there is a way to make x86 work like x64?".

UPDATE2: See my response about why this wasn't working on x86 and how to fix it.

Here's my test code:

test_exception.c

#include <stdlib.h>

void call_c() { exit(1); }

test_exception.cpp

#include <iostream>
#include <stdexcept>

extern "C" void call_c();

extern "C" void exit(int value)
{
  throw std::runtime_error(std::string("Throwing an exception: ") + char('0' + value));
}

int main()
{
  try {
    call_c();
  } catch (const std::exception &e) {
    std::cerr << e.what() << std::endl;
    return 1;
  }

  return 0;
}

Built with the following commands:

gcc -c test_exception.c -o test_exception_c.o
g++ -c test_exception.cpp -o test_exception_cpp.o
g++ test_exception_c.o test_exception_cpp.o -o test_exception
Dave Johansen
  • 889
  • 1
  • 7
  • 23
  • 1
    @iharob: But the question is about the interaction between C and C++ (specifically, throwing an exception in a function called from C). – Mike Seymour May 13 '15 at 16:21
  • Isn't there something possible like setting an `at_exit()` handler IIRC? – πάντα ῥεῖ May 13 '15 at 16:23
  • @MikeSeymour I see, is there a way to do that? I don't think you can link to a c++ library from a c program that uses c++ specific features like function overloading or exceptions. Suppose that you were to declare the function prototype in c, what would it look like? it would require `extern "C"` in the c++ program, which might prevent function overloading or exceptions, I am just guessing of course. – Iharob Al Asimi May 13 '15 at 16:23
  • 1
    @iharob: This is a C++ program with one function written in C, not a C program. As for what it would look like: it would look just like the code in the question. Perhaps you should read the question before commenting further. – Mike Seymour May 13 '15 at 16:25
  • Under what conditions does the library call `exit`? – David Schwartz May 13 '15 at 16:30
  • 1
    There are two reasons for a library to invoke `exit()`. The first is total and utter cluelessness of the programmer who thinks that this is a valid way to handle errors. I'd prefer keeping distance from such libraries. The other case is a fatal condition that normal error handling can't fix, like e.g. the vital plugin directory doesn't exist or some programming error is detected. Even there, using `abort()` instead to trigger the debugger would be the preferred choice. With that in mind, I'd second David's question why the library calls `exit()` at all. – Ulrich Eckhardt May 13 '15 at 16:32
  • FWIW this code works for me with GCC 4.8.1 on Linux; but other compilers might behave differently when exceptions propagate through C functions. – Mike Seymour May 13 '15 at 16:39
  • `exit()` is called under "error conditions when the program should close" but my question isn't "is this a good way to do things?". I have to use this library, can't change it, and I'm trying to make things play nicer with C++. – Dave Johansen May 13 '15 at 16:40
  • @MikeSeymour I had been trying this on 32-bit RHEL 6 (GCC 4.4.7-11) so I tried it on a 64-bit RHEL 7 machine (GCC 4.8.3-9) and it worked just fine. I then tried on a 64-bit RHEL 6 machine and it worked as well. So is this just a bug with GCC on 32-bit systems? – Dave Johansen May 13 '15 at 16:52
  • 3
    If all else fails, you can run the library via `fork` calls. – Gerard May 13 '15 at 16:53
  • 1
    @πάνταῥεῖ: `at_exit()` doesn't really help here. The program still terminates after any registered functions are called. You could have an `at_exit()`-registered function call something that continues doing the program's work, but when `exit()` is called again after that (including normal termination of `main()`), the behavior is undefined. – Keith Thompson May 13 '15 at 17:29
  • I don't have handy access to a x86 machine to check, but having done broadly similar things before, I suggest you look at gcc's `-nostdlib` and `-print-libgcc-file-name` options. What's happening, I think, is that `g++` is invoking the linker with the standard runtime _in front of_ the file with your redefinition of `exit`, and the linker search order therefore finds the standard `exit` first. You want the search order to be `main`, (your module defining `exit`), and _then_ `-lgcc`. So something like `g++ -nostdlib test_exception_c.o test_exception_cpp.o -o test_exception -lgcc` might do it. – Norman Gray May 13 '15 at 17:52
  • Afterthought: it's probably obvious, but you should use `g++ -v` to look at the linker line that `g++` generates, which may make it clear, by the order of the arguments to the linker, where the problem is coming from. – Norman Gray May 13 '15 at 18:09
  • @NormanGray The order of everything was basically the same with the minor exception being that x64 had extra paths to lib64. Here were the differences in parameters: "--with-arch=i686 vs --with-arch_32=i686", "-march=i686 vs Nothing" and "-m elf_i386 vs -m el_x86_64". – Dave Johansen May 13 '15 at 18:42
  • @DaveJohansen `exit` might well be in `lib64` (probably, but I don't know). Myself, I would try `-nostdlib` and friends, to push `lib64` to the end of the list. It would rule that out, at least. – Norman Gray May 13 '15 at 20:11
  • @NormanGray I know that my version of `exit` is being called because it prints that "terminate called after throwing ..." and that wouldn't be happening if my version of `exit` wasn't being called. Also, linking with `-nostdlib` causes a bunch of undefined references so I don't believe that that is the issue. – Dave Johansen May 13 '15 at 21:16
  • @DaveJohansen Ah well. Onward and upward, eh... – Norman Gray May 13 '15 at 21:23
  • Even if this worked, this would have the side-effect of breaking `exit` in legitimate cases … – Rufflewind May 17 '15 at 05:25

3 Answers3

2

I understand you may not want to read this, but it's most likely a mistake to continue executing a program after any part of it has attempted to call exit() or similar.

If part of the program called exit(), you have no guarantees about the state of the program at that point. You don't know if the heap is in a consistent state. You don't know if the stack is any good. If you convert the exit() into a throw, the very least you're likely to encounter are memory leaks each time this happens. You don't know if the library that caused this error can be safely called again.

If you have inspected the library's source code, and you're certain that no corruption will result, then the cleanest solution would be to modify the library itself, so that it throws instead of exiting.

If changing the library is not permissible, the other clean and correct solution is to put all usage of the library into a separate process which you can monitor and restart.

denis bider
  • 434
  • 3
  • 8
  • I agree completely with the advice. Sorry I wasn't more clear in the original post. My intention was not to "recall the library" or "stop the program from exiting", but just to let all of the destructors be called so that resources could be released properly and such. – Dave Johansen May 18 '15 at 15:39
  • As much as possible, a program should use resources in such a way that it's sufficient for the OS to release them when the program exits. There are circumstances, such as system shutdown, when the OS will kill your program without further notice. The program may also be killed by a user. It may be killed by an SSH server upon SSH session ending - which in turn may be caused by something as simple and uncontrollable as network disconnect. If logging or signaling or other output is required to indicate the reason for exit, I would trigger that from a function that executes on exit. Then, exit. – denis bider May 19 '15 at 13:36
1

The above code works, but on x86 before gcc 4.6, -fexceptions or -funwind-tables needs to be added when building the C code so that the stack unwinding can happen. You can see the details here.

Dave Johansen
  • 889
  • 1
  • 7
  • 23
0

Unless your C++ implementation is broken, it will call destructors for global variables when exit is called (GCC is not broken, as least all the versions I've tried). So you only need to clean up stuff that won't be cleaned up by destructors of global vars.

For global stuff on the heap, you can use atexit to register a cleanup function that will be called when exit is called -- this cleanup function can delete any heap objects that need to be cleaned up.

Destructors for stuff on the stack is much trickier. The best solution is probably to ensure that such destructors don't need to be called -- anything that MUST be cleaned up before exit should be referred to by a global (possibly using static members of classes) that does the cleanup in its destructor.

Chris Dodd
  • 119,907
  • 13
  • 134
  • 226