web-dev-qa-db-ja.com

exit()の代わりに_exit()を呼び出しても静的デストラクタが呼び出されないのは本当ですか?

[email protected]で、Greg Herlihyが次のextern "C"関数を投稿しました:

_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");                                                      
    }
} 
_

これはmain(int argc, char*argv[])とは異なるg ++エントリポイントです。

それからグレッグ・ハーリヒは書いた:

_exit()の呼び出し(おそらく "_Exit()"である必要があります)-C++グローバルオブジェクトが破棄されるのを防ぎません。 C++プログラムのグローバルオブジェクトの破棄は避けられません。代わりに、C++プログラムは独自のグローバルオブジェクトを破棄する責任があります。これは、プログラムがmain()から戻るか、exit()を呼び出すという2つの方法のいずれかで実行できます。そのため、main()の終了に失敗し、終了する前にexit()の呼び出しを無視するC++プログラムは、終了するまでにグローバルオブジェクトを破棄していません。

私はそれが標準に書かれていることから続くとは信じていません。

私が見る限り、exit()を呼び出すと、仕様では次のことが保証されます。

  • 自動保存期間を持つオブジェクトのデストラクタは呼び出されません。

  • 静的な保存期間を持つオブジェクトのデストラクタは、構築の逆の順序で呼び出されます。

心配なことは、標準ではexit()または_exit()について何も言われていないため、実装に依存する動作に依存しています。

そうではありません。 C++標準は、exit()の呼び出しがグローバルオブジェクトを破壊することを指定しています[3.6.3/1]そして_Exit()はC99標準の一部です(そしておそらく参照により次のC++標準に組み込まれます)。

そうです、C++標準はexit()が何をするかを述べていますが、_exit()_Exit()が何をするかは述べていません。また、C標準では、C++デストラクタについては何も述べられていません。

ここには実装定義の動作はありません。 _Exit()を呼び出しても、printf()を呼び出したり、exit()以外の関数を呼び出したりするよりも、C++プログラムのグローバルオブジェクトを破棄する可能性は高くありません。

_exit()および_Exit()は、登録された関数atexit()またはon_exit()を呼び出さないように指定されています。ただし、C++仕様では、ランタイムが静的な記憶期間を持つオブジェクトのデストラクタの呼び出しを処理するという意味が何も示されていないため、C++には役立ちません。準拠した実装では、atexit()またはon_exit()以外のメカニズムを使用して静的デストラクタを呼び出すことができます。

言い換えると、C++仕様は_exit()または_Exit()の動作について何も述べていません。したがって、どちらの関数を呼び出しても静的オブジェクトのデストラクタが実行されるかどうかについては想定できません。

コメントは大歓迎です。

5
Frank

そうです、C++標準は、exit()が何をするかについて述べていますが、_exit()または_Exit()が何をするかについては述べていません。

N3290から、私が間違っていない場合は、C++ 11標準であり、最後のわずかな編集上の変更を除きます。

関数_Exit(int status)には、この国際標準で追加の動作があります。

  • プログラムは、自動、スレッド、または静的ストレージ期間のオブジェクトのデストラクタを実行せず、atexit()に渡された関数を呼び出さずに終了します。

_exitの記述はありません(ただし、C標準では_exitも記述されていません)。

10
AProgrammer

言い換えると、C++仕様は_exit()または_Exit()の動作について何も述べていません。したがって、どちらの関数を呼び出しても静的オブジェクトのデストラクタが実行されるかどうかについては想定できません。

私たちが標準的な振る舞いについて話しているのなら、私にはわかりません。単純に指定されていない可能性があります。

ただし、私が遭遇したすべてのコンパイラ/プラットフォームは、Gregが書いたものと一致します。静的オブジェクトが起動時に初期化されず、終了時に破棄されないことを確認するために独自のC標準ランタイムライブラリ(CRT)を注入した場合、これは一種の障害になります。これをすべて手動で行う必要はありません。グローバル/静的のデストラクタはatexitフックステージで呼び出される傾向があり、_exitの主なポイントは、そのプロセス全体をバイパスすることです(そのため、通常、このようなオブジェクトの破棄をバイパスします)。

このコンテキストでデストラクタを呼び出さないようにすることが指定されていないかどうかは、_exitのポイント全体と矛盾します(これは、そのようなatexitクリーンアップフェーズを避けるためです)。 )。これらのデストラクタを呼び出すための代替メカニズムが言語内で利用可能であっても、おそらくそのようなシャットダウンメカニズムは_exitによって同様にバイパスされます。

つまり、std::vectorが規格で指定される前に連続しているという仮定のようなものです。要件に準拠した実装を実現する唯一の実用的な方法であったため、実際にはかなり合理的な仮定でした。

多分言語の弁護士がここに飛び込んで物事を片付けることができるかもしれませんが、_exitがグローバル/静的のデストラクタの呼び出しを回避することは非常に実用的な仮定だと思います。

5
user204677

Linuxでは、 _ exit(2)システムコール です(多くの場合 exit_group(2) が実際に呼び出されます)。したがって、そのrawを呼び出す場合 syscall 現在のプロセスが終了して_exit以外は何も起こりません

呼び出しプロセスを「即座に」終了します。

システムコールは、ユーザーモードの観点からは、カーネルモードに切り替える基本的な機械命令(SYSENTER)です。そして、カーネルは、_exitまたはexit_groupシステムコールの後で、プログラム(プロセスが存在しなくなったプログラム)に戻りません。そして、これはプログラミング言語やランタイムから独立しています。したがって、何も起こりません。_exitの後(または前)にコードが実行されないため、デストラクタ(またはその他)が実行されません。

もちろん exit(3) は標準のCライブラリ関数です:

atexit(3) および on_exit(3) で登録されたすべての関数は、登録の逆の順序で呼び出されます。

すべてのオープン stdio(3) ストリームはフラッシュされて閉じられます。 tmpfile(3) で作成されたファイルは削除されます。

そしてグローバルデストラクタも呼ばれます。実際、私はそれらを呼び出す何かが crtat_exitルーチンの_startに相当するものによって登録されていると思います。

Linuxでは C標準ライブラリ は通常 フリーソフトウェア なので、exit;のソースコードを調べることができます。 musl-libc の場合 exit/exit.c ソースファイルは非常に読みやすいです。

POSIX exitの仕様 も読んでください。

今日の調査とテストで、静的オブジェクトのデストラクタはここでは問題ではないことがわかりました。次のC++プログラムを動作させました。

#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;

}
0
Frank