Reputation: 449
I have a cross platform C++ application build with C++ Builder 10.1 Berlin and have a problem understanding the lifetime handling of objects, in this case strings, wich are declared outside the class. I have created a new forms application and added some code. The cpp file looks like this:
#include
#pragma hdrstop
#include "FmrMain.h"
#pragma package(smart_init)
#pragma resource "*.fmx"
TForm1 *Form1;
const String Hello = "Hello";
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
ShowMessage(Hello);
}
void __fastcall TForm1::FormDestroy(TObject *Sender)
{
ShowMessage(Hello);
}
I compile this with the CLANG enhanced C++11 compiler bcc32c, run the application and close the form again. When TForm1::FormDestroy is called Hello is allready destroyed. When I compile the code for win32 with the classic compiler bcc32 the string is destroyed after FormDestroy.
Can someone explain this or provide some information about the topics I have to look for? Why is the CLANG based compiler behaving different here?
Edit
It's easier to debug when I use a self defined class instead of a string.
class Foo {
public:
Foo(){};
~Foo(){}
};
Foo A;
//--------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//--------------------------------------------------------------------------
__fastcall TForm1::~TForm1()
{
}
The creation and destruction oder is like this. I have added the call stacks.
bcc32c (CLANG C++11 compiler)
create Foo
:004052C0 Foo(this=:00400000)
:00405070 __cxx_global_var_init3()
:004052A3 _GLOBAL__I_a()
:00405ab7 ; ~Foo
:321fa2b7 ; C:\Program Files (x86)\Embarcadero\Studio\18.0\bin\CC32C240MT.DLL
:321fa6ff CC32C240MT.__wstartup + 0xbb
create Form1
:004052EC TForm1(this=:00402422, __ctor_flag='\0')
:0085c139 fmx240.@Fmx@Forms@TApplication@CreateForm$qqrxp17System@TMetaClasspv + 0x5d
:0085c349 fmx240.@Fmx@Forms@TApplication@RealCreateForms$qqrv + 0x81
destroy Foo
:004052D0 ~Foo(this=:0040B7DC)
:0040509E __dtor_A()
:321f6246 CC32C240MT.___call_atexit_procs + 0x52
:321f671c CC32C240MT.___exit + 0x20
destroy Form1
:00405868 ~TForm1(this=:5016E698)
bcc32 (Classic borland compiler)
create Foo
:00404950 Foo::Foo(this=:00409B74)
:004048A0 STCON0()
:00405727 ; IRoot
:322190f1 ; C:\Program Files (x86)\Embarcadero\Studio\18.0\bin\CC32240MT.DLL > :322193b5 CC32240MT.__wstartup + 0xa5
create Form1
:00404994 TForm1::TForm1(this=:02F2AE20, Owner=:02F39620)
:0095c139 fmx240.@Fmx@Forms@TApplication@CreateForm$qqrxp17System@TMetaClasspv + 0x5d
:0095c349 fmx240.@Fmx@Forms@TApplication@RealCreateForms$qqrv + 0x81
destroy Form1
:00404ABC TForm1::~TForm1(this=:02F2AE20)
destroy Foo
:00404978 Foo::~Foo(this=:00409B74)
:0040493F STDES0()
:0040573f ;IRoot>
:3221910f ; C:\Program Files(x86)\Embarcadero\Studio\18.0\bin\CC32240MT.DLL
:3221915b ; C:\Program Files (x86)\Embarcadero\Studio\18.0\bin\CC32240MT.DLL > :3221944a ; C:\Program Files (x86)\Embarcadero\Studio\18.0\bin\CC32240MT.DLL
Upvotes: 3
Views: 1629
Reputation: 597941
Auto-created TForm
objects are owned by the global TApplication
object. That object is destroyed (thus destroying its owned Forms) after the application's main()
/wmain()
/WinMain()
entry point function has exited. Globals are destroyed during application cleanup.
The lifetime of your global String
is not guaranteed to outlive the lifetime of the global TApplication
object, in either compiler. You are relying on undefined behavior based on the cleanup order of globals across different units. And worse, you are relying on the cleanup order across different frameworks! Your String
is in your own C++ code, but the TApplication
object is in the Delphi-based RTL library.
If your String
needs to remain alive for the lifetime of the TForm
that uses it, you should declare it as a static
member of that class instead:
FmrMain.h:
//---------------------------------------------------------------------------
#ifndef FmrMainH
#define FmrMainH
//---------------------------------------------------------------------------
#include <System.Classes.hpp>
#include <FMX.Controls.hpp>
#include <FMX.Forms.hpp>
//---------------------------------------------------------------------------
class TForm1 : public TForm
{
__published: // IDE-managed Components
private: // User declarations
static const String Hello;
public: // User declarations
__fastcall TForm1(TComponent* Owner);
__fastcall ~TForm1();
};
//---------------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//---------------------------------------------------------------------------
#endif
FmrMain.cpp:
//---------------------------------------------------------------------------
#include <fmx.h>
#pragma hdrstop
#include "FmrMain.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.fmx"
TForm1 *Form1;
const String TForm1::Hello = "Hello";
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
ShowMessage(Hello);
}
void __fastcall TForm1::~TForm1()
{
ShowMessage(Hello);
}
//---------------------------------------------------------------------------
Alternatively, use a wchar_t*
instead of a String
, then you don't run into any cleanup issues:
//---------------------------------------------------------------------------
#include <fmx.h>
#pragma hdrstop
#include "FmrMain.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.fmx"
TForm1 *Form1;
static const wchar_t* Hello = L"Hello";
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
ShowMessage(Hello);
}
void __fastcall TForm1::~TForm1()
{
ShowMessage(Hello);
}
//---------------------------------------------------------------------------
Upvotes: 5