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

问题描述 投票:0回答:1

对于以下代码

struct X {
  int a;
};

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

clang 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 &&'

我尝试获取

CXXRecordDecl
struct X
,发现
decl->getDestructor()
返回
nullptr

这让我有点困惑,因为标准规定,没有用户声明的析构函数的类将有一个隐式声明的析构函数,并且在使用 odr 时它将是隐式定义的。那么什么是 odr-使用析构函数?这个

X
的析构函数是odr使用的吗?或者这只是我对 clang AST 和
clang::CXXRecordDecl::getDestructor
行为的误解?

请注意,相比之下,clang-AST 显示定义了

X
的默认构造函数,即使它不执行任何操作。

c++ clang destructor one-definition-rule libtooling
1个回答
0
投票

这个析构函数是 ODR 使用的吗?

是的。引用basic.def.odr

如果类的析构函数可能被调用,则它是 odr-used。

其中“潜在调用”有一个不平凡的定义,但简而言之, 此处为 true,因为

main()
声明了一个
X
类型的对象。

Clang
getDestructor()
nullptr
是什么意思?

文档

CXXRecordDecl::getDestructor()
只是说:

返回此类的析构函数 decl。

甚至不承认它可以返回

nullptr
,更不用说 解释这意味着什么。基于阅读源代码,我 结论这意味着析构函数是微不足道的,并且没有一个 无论如何都会导致创建隐式声明的条件 (见下文)已经很满意了。

它确实not意味着Clang声称析构函数不是 使用 ODR。这

Decl::isUsed()
方法声称报告此信息(请参阅有关
setIsUsed()
),但似乎并不完全准确;甚至 如果我通过添加一个来创建析构函数声明
virtual
函数,析构函数还没有标记
isUsed()

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

是的——但为了保持一致,Clang 只需要生成编译后的输出 其行为“就好像”析构函数已被定义。缺少一个 特定的 AST 节点不会使其不合格。即使缺乏一个 编译目标文件中的定义则不然,只要它遵循 相关的ABI,从而配合其他工具,再次实现 所需的“好像”行为(假设源代码 符合单一定义规则)。

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

这似乎没有记录,所以我试图从 来源。隐式析构函数声明是通过以下方式创建的

Sema::DeclareImplicitDestructor()
SemaDeclCXX.cpp:13803
。 这个函数在好几个地方都被调用了,我没有全部遵循 链条向后,但对于第一个近似值,这不会 调用如果:

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

请注意,这是最后一个条件,由于

x
的声明,导致 Clang 声明三个隐式 con讲师。

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

是的!您可以致电

Sema::ForceDeclarationOfImplicitMembers
CXXRecordDecl
上强制 Clang 甚至声明隐式成员 否则它就不会这样做。例如,就在之后 解析时,您可以使用
RecursiveASTVisitor
遍历 AST 并在每个
CXXRecordDecl
上调用此方法。

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

我如何获得
Sema
对象?

您无法从

ASTContext
获取它,因为那只是 AST, 而
Sema
创建 AST 过程的一部分。 (如果你 如果将 AST 保存到磁盘然后再加载回来,就不会出现
Sema
根本就是物体。)

我的首选方法是使用

ASTUnit::LoadFromCompilerInvocation
进行初始解析。这会立即产生一个
ASTUnit
对象, 从中您可以获得
Sema
(通过
getSema()
)和
ASTContext
(通过
getASTContext()
)。

如果您使用的是

ClangTool
, 这就是 教程 指向人们,你必须使用它的
buildASTs
方法而不是
run
。这可能需要对您的分析方式进行一些重构 但是,因为在这种情况下它不会调用
ToolAction

© www.soinside.com 2019 - 2024. All rights reserved.