关于 clang AST 和 odr - 析构函数的使用

About clang AST and odr-use of destructors

提问人:GKxx 提问时间:9/12/2023 最后编辑:Scott McPeakGKxx 更新时间:9/23/2023 访问量:64

问:

对于以下代码

struct X {
  int a;
};

int main() {
  X x;
  return 0;
}

叮当声 AST 不显示:DestructorDecl

CXXRecordDecl 0x55a415f54f00 </home/gkxx/exercises/smfgen/tmp/../tmp/a.cpp:1:1, line:4:1> line:1:8 referenced struct X definition
|-DefinitionData pass_in_registers aggregate standard_layout trivially_copyable pod trivial literal
| |-DefaultConstructor exists trivial
| |-CopyConstructor simple trivial has_const_param implicit_has_const_param
| |-MoveConstructor exists simple trivial
| |-CopyAssignment simple trivial has_const_param needs_implicit implicit_has_const_param
| |-MoveAssignment exists simple trivial needs_implicit
| `-Destructor simple irrelevant trivial needs_implicit
|-CXXRecordDecl 0x55a415f55018 <col:1, col:8> col:8 implicit struct X
|-FieldDecl 0x55a415f550c0 <line:2:3, col:7> col:7 a 'int'
|-CXXConstructorDecl 0x55a415f55320 <line:1:8> col:8 implicit used X 'void () noexcept' inline default trivial
| `-CompoundStmt 0x55a415f557d8 <col:8>
|-CXXConstructorDecl 0x55a415f55458 <col:8> col:8 implicit constexpr X 'void (const X &)' inline default trivial noexcept-unevaluated 0x55a415f55458
| `-ParmVarDecl 0x55a415f55568 <col:8> col:8 'const X &'
`-CXXConstructorDecl 0x55a415f55618 <col:8> col:8 implicit constexpr X 'void (X &&)' inline default trivial noexcept-unevaluated 0x55a415f55618
  `-ParmVarDecl 0x55a415f55728 <col:8> col:8 'X &&'

我试图得到,发现返回.CXXRecordDeclstruct Xdecl->getDestructor()nullptr

这让我有点困惑,因为标准说,没有用户声明的析构函数的类将有一个隐式声明的析构函数,并且在使用 odr 时它将被隐式定义。那么什么是odr使用的析构函数呢?这个 odr 的析构函数是否使用过?或者这只是关于我对 clang AST 和行为的误解?Xclang::CXXRecordDecl::getDestructor

请注意,相比之下,clang-AST 显示 的默认构造函数已定义,即使它不执行任何操作。X

C++ clang 析构函数 one-definition-rule libtooling

评论

0赞 Öö Tiib 9/12/2023
“微不足道”的事情什么都不做,所以可能不被召唤。但是默认构造函数本身的存在是重要的事实(即使微不足道)。
0赞 Scott McPeak 9/13/2023
由于问题与 clang “libtooling” C++ API 有关,因此我添加了该标签。

答:

1赞 Scott McPeak 9/13/2023 #1

是否使用了此析构函数 ODR?

是的。引用 basic.def.odr

如果可能调用类的析构函数,则使用 odr。

其中“潜在调用”有一个不平凡的定义,但简而言之, 在这里为 true,因为声明了一个 类型的对象。main()X

Clang 是什么意思?getDestructor()nullptr

CXXRecordDecl::getDestructor() 的文档只是说:

返回此类的析构函数 decl。

甚至不承认它可以返回,更不用说了 解释这意味着什么。根据阅读源码,我 得出结论,这意味着析构函数是微不足道的,并且没有一个 无论如何都会导致创建隐式声明的条件 (见下文)已经满意了。nullptr

这并不意味着 Clang 声称析构函数不是 ODR 使用。Decl::isUsed() 方法声称可以报告此信息(参见 上的文档),但它似乎并不完全准确;甚至 如果我通过添加函数来创建析构函数声明,则析构函数仍然没有被标记。setIsUsed()virtualisUsed()

标准不是说应该在这里定义析构函数吗?

是的,但为了符合要求,Clang 只需要生成编译的输出 它的行为“就好像”析构函数已被定义一样。缺少 特定的 AST 节点不会使其不符合要求。即使缺乏 编译对象文件中的定义不会,只要它遵循 相关的 ABI,从而与其他工具合作,再次实现 所需的“仿佛”行为(假设源代码 符合一个定义规则)。

Clang 创建隐式声明的条件是什么?

这似乎没有记录在案,所以我试图从 来源。隐式析构函数声明由 SemaDeclCXX.cpp:13803 创建。 这个函数在几个地方被调用,我没有遵循所有 链向后,但对于第一个近似值,这不会是 如果出现以下情况,则称为:Sema::DeclareImplicitDestructor()

  • 析构函数本身是微不足道的,并且
  • 该类没有 vtable,并且
  • 析构函数之间不需要过载解决方案(这可能 需要 )。requires

请注意,这是最后一个条件,由于 的声明,这导致 Clang 声明三个隐式结构函数。x

我是否可以强制声明析构函数?

是的!您可以在 a 上调用 Sema::ForceDeclarationOfImplicitMembers 来强制 Clang 声明隐式成员,甚至 否则它不会这样做。例如,紧接着 解析时,可以使用 RecursiveASTVisitor 遍历 AST 并在每个 .CXXRecordDeclCXXRecordDecl

完成此操作后,所有隐式成员都将可用。

我将如何获得对象?Sema

你不能从 中得到它,因为那只是 AST, while 是创建 AST 过程的一部分。(如果你 如果将 AST 保存到磁盘,然后将其加载回去,则根本不会有对象。ASTContextSemaSema

我的首选方法是使用 ASTUnit::LoadFromCompilerInvocation 进行初始解析。这会立即产生一个对象, 您可以从中获取 (via ) 和 (via )。ASTUnitSemagetSema()ASTContextgetASTContext()

如果您改用 ClangTool::run, 这就是本教程指向人们的内容:

Tool.run(
  clang::tooling::newFrontendActionFactory<MyASTFrontendAction>().get());

抓住 :CompilerInstanceCreateASTConsumer

class MyASTFrontendAction : public clang::ASTFrontendAction {
public:
  virtual unique_ptr<clang::ASTConsumer> CreateASTConsumer(
    clang::CompilerInstance &ci,
    llvm::StringRef inFile) override
  {
    return make_unique<MyASTConsumer>(ci);
  }
};

然后将其存储在 中,并在需要时使用它:ASTConsumergetSema()

class MyASTConsumer : public clang::ASTConsumer {
public:
  clang::CompilerInstance &m_compilerInstance;

  MyASTConsumer(clang::CompilerInstance &compilerInstance)
    : m_compilerInstance(compilerInstance)
  {}

  virtual void HandleTranslationUnit(clang::ASTContext &Context) override
  {
    clang::Sema &sema = m_compilerInstance.getSema();

    // ...
  }
};

评论

0赞 GKxx 9/15/2023
感谢您对我的两个问题的精彩回答! 是的,我认为文档(和教程)非常简短和抽象。那么,你是否主要通过阅读源代码发现了所有这些东西?你能给我推荐一些更友好的相关材料来学习 clang libtoolling 吗?多谢。The documentation of CXXRecordDecl::getDestructor() just says "Returns the destructor decl for this class." without even acknowledging that it can return nullptr, let alone explaining what that would mean.
0赞 Scott McPeak 9/16/2023
别客气!我主要是通过阅读文档、挖掘源代码和实验来了解这一点的。如果你还没有,请查看内部手册,其中有一些很好的信息。除此之外,我正在编写其他文档:Clang PR 66436
0赞 GKxx 9/23/2023
我发现通过简单地调用 来获取 Sema 对象要容易得多(我的意思是,重构更少),这可以在 .getSema()CompilerInstanceASTFrontendAction::CreateASTConsumer
0赞 GKxx 9/23/2023
现在,隐式声明是由 生成的,但标准要求隐式定义的那些声明可能仍未定义。我找到了可用于定义它们的函数,但是如何确定是否使用了特殊成员函数?我也找到了函数,但在这种情况下它似乎生成了一个空向量。Sema::ForceDeclarationOfImplicitMembersSema::DefineImplicitxxxSema::getUndefinedButUsed()
0赞 Scott McPeak 9/23/2023
@GKxx 感谢您的提示;我错过了通过 API 的那条路线(答案相应地编辑了)。至于确定 odr 用法,我唯一知道的是 ,但它似乎并不准确(或者我可能误解了 ord-usage 或该 API 的目的)。我建议提出一个新问题,专门针对这些析构函数情况从 Clang API 获取 odr 用法。CompilerInstanceDecl::isUsed()