【问题标题】:Difference between bcc32 and bcc32c object lifetimebcc32 和 bcc32c 对象生命周期之间的区别
【发布时间】:2016-06-13 06:48:23
【问题描述】:

我有一个使用 C++ Builder 10.1 Berlin 构建的跨平台 C++ 应用程序,并且在理解对象的生命周期处理时遇到了问题,在这种情况下,字符串是在类之外声明的。 我创建了一个新的表单应用程序并添加了一些代码。 cpp 文件如下所示:

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

我使用 CLANG 增强型 C++11 编译器 bcc32c 编译它,运行应用程序并再次关闭表单。当 TForm1::FormDestroy 被调用时,Hello 已经被销毁。当我使用经典编译器 bcc32 编译 win32 的代码时,字符串在 FormDestroy 之后被销毁。

有人可以对此进行解释或提供一些关于我必须寻找的主题的信息吗?为什么基于 CLANG 的编译器在这里表现不同?

编辑

当我使用自定义类而不是字符串时,调试起来更容易。

class Foo {
public:
   Foo(){};
  ~Foo(){}
};
Foo A;

//--------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//--------------------------------------------------------------------------

__fastcall TForm1::~TForm1()
{
}

创造与毁灭秩序是这样的。我已经添加了调用堆栈。

bcc32c(CLANG C++11 编译器)

  1. 创建 Foo

    :004052C0 Foo(this=:00400000)
    :00405070 __cxx_global_var_init3()
    :004052A3 _GLOBAL__I_a()
    :00405ab7 ; ~福
    :321fa2b7 ; C:\Program Files (x86)\Embarcadero\Studio\18.0\bin\CC32C240MT.DLL
    :321fa6ff CC32C240MT.__wstartup + 0xbb

  2. 创建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

  3. 摧毁 Foo

    :004052D0 ~Foo(this=:0040B7DC)
    :0040509E __dtor_A()
    :321f6246 CC32C240MT.___call_atexit_procs + 0x52
    :321f671c CC​​32C240MT.___exit + 0x20

  4. 销毁 Form1

    :00405868 ~TForm1(this=:5016E698)

bcc32(经典 borland 编译器)

  1. 创建 Foo

    :00404950 Foo::Foo(this=:00409B74)
    :004048A0 STCON0()
    :00405727 ;根
    :322190f1 ; C:\Program Files (x86)\Embarcadero\Studio\18.0\bin\CC32240MT.DLL > :322193b5 CC32240MT.__wstartup + 0xa5

  2. 创建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

  3. 销毁 Form1

    :00404ABC TForm1::~TForm1(this=:02F2AE20)

  4. 摧毁 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

【问题讨论】:

  • const String Hello 是一个常量对象。它不应该在 main 函数返回之前被销毁。可能它从未被创建,编译器创建了一个字符串常量,而不是作为ShowMessage 函数的参数。您可以使用调试器进行检查。
  • 您的带有自定义类的示例值得在析构函数中设置断点。查看 Form1 析构函数的调用堆栈。我怀疑 Form1 不是在 hesp 上创建的,而是作为文件范围验证的。在这种情况下,您有一个实现定义的对构造函数和析构函数的调用顺序。这会导致您的程序出现未定义的行为。
  • 如果 ShowMessage 是系统函数,那么您必须切换到反汇编视图。使用 ABI 规范如何将参数传递给函数并查看内存转储。所以你可以看到传递给函数的内容。
  • 是什么导致这些东西被破坏?它是显式的程序代码,还是作为程序终止的一部分被销毁?在后一种情况下,我不认为 C++ 对销毁静态分配的对象的顺序提供任何保证。
  • @KeremD: TObject 派生类,包括TForm总是在堆上分配。在您的示例中,String 位于全局内存中,并在 RTL 销毁拥有您的 TForm1 对象的全局 TApplication 对象之前被编译器释放。 IOW,您的TForm1 比您的String 寿命更长,这就是为什么它在您的OnDestroy 事件处理程序中为NULL(顺便说一句,您永远不应该在C++ 中使用!改用~TForm1 析构函数)。

标签: c++ clang c++builder firemonkey object-lifetime


【解决方案1】:

自动创建的TForm 对象归全局TApplication 对象所有。 应用程序的main()/wmain()/WinMain() 入口点函数退出之后,该对象被销毁(从而销毁其拥有的表单)。全局在应用程序清理期间被销毁。

在任一编译器中,保证全局 String 的生命周期长于全局 TApplication 对象的生命周期。您依赖于基于跨不同单元的全局变量的清理顺序的未定义行为。更糟糕的是,您依赖于跨不同框架的清理顺序!您的 String 在您自己的 C++ 代码中,但 TApplication 对象在基于 Delphi 的 RTL 库中。

如果您的String 需要在使用它的TForm 的生命周期内保持活动状态,则应将其声明为该类的static 成员:

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);
}
//---------------------------------------------------------------------------

或者,使用wchar_t* 而不是String,那么您不会遇到任何清理问题:

//---------------------------------------------------------------------------
#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);
}
//---------------------------------------------------------------------------

【讨论】:

  • @RemyLebau:感谢这个不错的答案。我已经对此感到害怕,因为我们使用这种技术来定义常量字符串很长一段时间......我还尝试按照您建议的方式将字符串定义为静态类成员。同样在这种情况下,HelloForm1 之前被 gestroyed,并且在调用 Form1 的析构函数时是 NULL。这太令人沮丧了,我将字符串定义为wchar_t* 并在这里提出了问题。
  • 我查看了What is the lifetime of a static variable in a C++ function?What is the lifetime of class static variables in C++?。在 [class.static.data] C++ 标准中说:“静态数据成员的初始化和销毁​​就像非局部变量一样”。我认为静态成员TForm1::Hello 也像在类外定义的静态变量一样被构造和破坏。
猜你喜欢
  • 2011-04-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-04
  • 2020-08-26
  • 2017-12-18
  • 1970-01-01
  • 2013-10-29
相关资源
最近更新 更多