6

On [email protected], Greg Herlihy posted the following extern "C" function:

extern "C" 
{
    int func()
    {
        wchar_t memoryName[256];
        wchar_t mutexName[256];
        wchar_t eventName[256];
        mbstowcs(memoryName, "MemoryName", 256);
        mbstowcs(mutexName, "MutexName", 256);
        mbstowcs(eventName, "EventName", 256);
        std::wstring memoryString(memoryName);
        std::wstring mutexString(mutexName);
        std::wstring eventString(eventName);
        CDataTransferServer *srv = new CDataTransferServer();
        srv->Initialize(1, CC_SAMPLETYPE_MPEG4,128,256,64);
        printf("Inside entry point tester 1\n");
        srv->AddUser(5, memoryString, mutexString, eventString);
        printf("Inside entry point tester 2\n");
        delete srv;
        printf("Exiting entry point tester 3\n");                                                      
    }
} 

which is a g++ entry point different than main(int argc, char*argv[]).

Greg Herlihy then wrote:

Calling _exit() (which presumably should be "_Exit()") - does not "prevent" C++ global objects from being destroyed. The destruction of a C++ program's global objects is not inevitable. Instead a C++ program is responsible for destroying its own global objects - and can do so in one of two ways: the program returns from main() or it calls exit(). So a C++ program that fails to exit main() and neglects to call exit() before it terminates - will not have destroyed its global objects by the time it ended.

I don't believe that follows from what is written in the standard.

As far as I can see, when I call exit(), the spec guarantees that:

  • Destructors for objects with automatic storage duration will not be called.

  • Destructors for objects with with static storage duration will be called, in reverse order of construction.

The worrying thing is that the standard doesn't say anything about exit() or _exit(), so I'm relying on implementation-dependent behavior.

Not so. The C++ Standard specifies that calling exit() destroys global objects[3.6.3/1] And _Exit() is part of the C99 Standard (and will presumably be incorporated into the next C++ Standard by reference).

Right, the C++ standard says what exit() does, but it doesn't say what _exit() or _Exit() do. And the C standard certainly doesn't say anything about C++ destructors.

I don't see any implementation-defined behavior here. Calling _Exit() is no more likely to destroy a C++ program's global objects than calling printf() - or calling any other function that is not exit().

_exit() and _Exit() are specified not to call any functions registered atexit() or on_exit(). However, that's not useful for C++ because the C++ spec doesn't say by what means the runtime takes care of calling destructors of objects with static storage duration. A compliant implementation could use a mechanism other than atexit() or on_exit() to invoke static destructors.

In other words, the C++ spec does not say anything all about the behavior of _exit() or _Exit(). Therefore, I cannot make any assumptions about whether or not calling either function will cause destructors of static objects to run or not.

Any comments are welcome.

4
  • How is this in any way unclear or primarily opinion-based? It is well-researched, focused, and should have an answer based on language specifications or compiler implementations.
    – user22815
    Commented Jan 6, 2016 at 1:32
  • @Snowman, Could you please look at the 4th answer which was just posted? Also, are there certain memory mapped addresses that cannot be accessed without a Linux bus error? Thanks
    – Frank
    Commented Jan 6, 2016 at 2:46
  • @Frank Answers have no order, they're shuffled around to let good ones bubble up. Commented Jan 6, 2016 at 3:24
  • @Lars Viklund, Are there certain memory mapped addresses returned by mmap() that cannot be accessed without a Linux bus error? How do I fix bus errors. Thank you.
    – Frank
    Commented Jan 6, 2016 at 5:22

4 Answers 4

10

Right, the C++ standard says what exit() does, but it doesn't say what _exit() or _Exit() do.

From n3290 which, if I'm not mistaken, is the C++11 standard excepted minor last minute editorial changes:

The function _Exit(int status) has additional behavior in this International Standard:

  • The program is terminated without executing destructors for objects of automatic, thread, or static storage duration and without calling functions passed to atexit()

There is no description of _exit (but then the C standard does not describe _exit either).

1
  • Very good catch!
    – user204677
    Commented Jan 4, 2016 at 15:52
6

In other words, the C++ spec does not say anything all about the behavior of _exit() or _Exit(). Therefore, I cannot make any assumptions about whether or not calling either function will cause destructors of static objects to run or not.

If we're talking about standard behavior, I'm afraid I don't know. It might very well simply be unspecified.

However, every compiler/platform I've encountered aligns with what Greg wrote, and it's kind of a stumbling point if you ever inject your own C standard runtime library (CRT) to find that static objects are neither initialized on startup nor destroyed on exit without doing this all manually. Destructors for globals/statics tend to be invoked in the atexit hook stage, and the main point of _exit is to bypass that whole process (and thereby typically bypass destruction of such objects).

It makes practical sense whether or not it's unspecified to avoid invoking destructors in this context, since to do so would kind of contradict the whole point of _exit (which is to avoid that kind of atexit cleanup phase). Even if alternate mechanisms were available within the language to invoke these destructors, probably such shutdown mechanisms would likewise be bypassed by _exit.

So it's kind of like the assumptions that std::vector is contiguous before the standard specified it. It ended up being a pretty reasonable assumption in practice since that was about the only practical way to end up with an implementation that conformed to its requirements.

Maybe a language lawyer could jump in here and clear things up, but I'd suspect that it's a very practical assumption that _exit avoids invoking destructors for globals/statics.

4

On Linux, _exit(2) is a system call (often exit_group(2) is actually called). So if you call that raw syscall nothing else happens except that the current process is ending and _exit

terminates the calling process "immediately".

A system call is, from the user-mode point of view, an elementary machine instruction (SYSENTER) which switches to kernel mode. And the kernel won't return into your program (whose process ceases to exist) after an _exit or exit_group syscall. And this is independent of the programming language and runtime. So nothing else happens, no code is running after (or before) the _exit, so no destructors (or anything else) is run.

Of course, exit(3) is a standard C library function :

All functions registered with atexit(3) and on_exit(3) are called, in the reverse order of their registration.

All open stdio(3) streams are flushed and closed. Files created by tmpfile(3) are removed.

And global destructors are also called. Actually, I guess that something calling them is registered by some equivalent of at_exit in the _start routine of crt0.

Since your C standard library is generally free software on Linux, you could study the source code of exit; for musl-libc its exit/exit.c source file is quite readable.

Read also the POSIX specification of exit.

1
  • The function _exit is not a system call. In glibc on Linux, it is an infinite loop of three system calls, none of which are called _exit. Each of them should in theory behave as you describe, destroying the process and not returning to it, so usually it will immediately call the exit_group syscall as you say; that doesn't make it a syscall in itself, and it certainly doesn't stop a programming language from defining a function called _exit that does something else.
    – Daniel H
    Commented Apr 15, 2021 at 2:23
0

I found by research and testing today that the destructors of static objects is not the issue here. I got the following C++ program to work.

#include  <stdio.h>
#include  <iostream>
#include  <dlfcn.h>
#include "CameraControlDefs.h"
#include "DataServer.h"

using namespace std;


int main(int argc,char** argv)
{

   bool (*sayHello)(CDataTransferServer* _this, int nCameraID, CC_SAMPLETYPE nDataType,
                                unsigned int nImageWidth, unsigned int nImageHeight,
                                unsigned int nMaxFrames);


 // API api;
  unsigned int tmp;

  //...

  void* handle = dlopen("libDataServer.so", RTLD_LAZY);
  if (!handle)
  {
    std::cerr << dlerror() << std::endl;
    return 1;
  }

  // load the symbols
    create_t* create_triangle = (create_t*) dlsym(handle, "create");
    const char* dlsym_error = dlerror();
    if (dlsym_error) {
        cerr << "Cannot load symbol create: " << dlsym_error << '\n';
        return 1;
    }

    destroy_t* destroy_triangle = (destroy_t*) dlsym(handle, "destroy");
    dlsym_error = dlerror();
    if (dlsym_error) {
        cerr << "Cannot load symbol destroy: " << dlsym_error << '\n';
        return 1;
    }

    // create an instance of the class
    CDataTransferServer* poly = create_triangle();


  *(bool **)(&sayHello) = (bool *)dlsym(handle,"_ZN19CDataTransferServer10InitializeEi13CC_SAMPLETYPEjjj");
  if (dlerror())
  {
    std::cerr << dlerror() << std::endl;
    return 2;
  }


  printf("ywc = %x\n",poly);


// destroy the class

   (*sayHello)(poly, 5, CC_SAMPLETYPE_MPEG4,128,256,64);
    destroy_triangle(poly);

    // unload the triangle library
  dlclose(handle);

    //...

  return 0;

}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.