如何在Visual Studio调用堆栈中读取匿名lambda的名称?

How to read name of anonymous lambdas in Visual Studio call stack?

提问人:Kaiyakha 提问时间:11/10/2023 最后编辑:Remy LebeauKaiyakha 更新时间:11/11/2023 访问量:78

问:

在调用堆栈中跟踪匿名 lambda 对我来说一直很有挑战性,所以我编写了一些沙盒代码来处理这个问题:

int main() {
  [] {
    std::cout << "Hello World!\n";
  } ();

  [] {
    std::cout << "Hello World!\n";
  } ();
}

在这里,我调用了匿名 lambda。我在 lambda 的主体中放置断点并检查调用堆栈。我看到的是:

`main'::`2'::<lambda_1>::operator()()
`main'::`2'::<lambda_2>::operator()()

我主要是通过在不同的函数或命名空间中使用匿名 lambda 来找出记录的含义。

我无法理解的一件事是这个数字将函数的名称和 lambda 的标签分开。有谁知道这个数字是什么意思?2main<lambda_1>

C++ Visual-Studio 调试 匿名函数 调用堆栈

评论

0赞 underloaded_operator 11/10/2023
我不完全确定,但我相信它们是编译器生成的。它生成这些唯一编号(在本例中为“2”)以区分两个 lambda 实例。
0赞 Kaiyakha 11/10/2023
@underloaded_operator它不依赖于经过测试的 lambda 的数量
0赞 underloaded_operator 11/10/2023
您是否尝试过使用不同的编译器进行编译?
0赞 Kaiyakha 11/10/2023
@underloaded_operator不,我使用 msvc。无论如何,我大部分时间都在使用它,所以我想知道 msvc 生成的记录的含义
1赞 Minxin Yu - MSFT 11/10/2023
它是 ,而不是 '2'。也许它会标记 Lambda 表达式。而且它没有记录在案。__l2

答:

1赞 Sedenion 11/11/2023 #1

它来自标识嵌套块范围的残缺名称的一部分。示例(godbolt):`2'

void foo() 
{
  [] { int i = 11; } (); // ??R<lambda_1>@?1??foo@@YAXXZ@QEBA@XZ => `foo'::`2'::<lambda_1>::operator()
  [] { int i = 12; } (); // ??R<lambda_2>@?1??foo@@YAXXZ@QEBA@XZ => `foo'::`2'::<lambda_2>::operator()

  {
    [] { int i = 21; } (); // ??R<lambda_3>@?2??foo@@YAXXZ@QEBA@XZ => `foo'::`3'::<lambda_3>::operator()
    [] { int i = 21; } (); // ??R<lambda_4>@?2??foo@@YAXXZ@QEBA@XZ => `foo'::`3'::<lambda_4>::operator()
  }

  {
    [] { int i = 31; } (); // ??R<lambda_5>@?3??foo@@YAXXZ@QEBA@XZ => `foo'::`4'::<lambda_5>::operator()
    [] { int i = 31; } (); // ??R<lambda_6>@?3??foo@@YAXXZ@QEBA@XZ => `foo'::`4'::<lambda_6>::operator()

    {
        [] { int i = 41; } (); // ??R<lambda_7>@?4??foo@@YAXXZ@QEBA@XZ => `foo'::`5'::<lambda_7>::operator()
    }
  }

  {
    [] { int i = 51; } (); // ??R<lambda_8>@?5??foo@@YAXXZ@QEBA@XZ => `foo'::`6'::<lambda_8>::operator()
    [] { int i = 52; } (); // ??R<lambda_9>@?5??foo@@YAXXZ@QEBA@XZ => `foo'::`6'::<lambda_9>::operator()
  }

  [] { int i = 13; } (); // ??R<lambda_8>@?1??foo@@YAXXZ@QEBA@XZ => `foo'::`2'::<lambda_8>::operator()
}

因此表示功能级别块,数字越大枚举内部块。 这只是 MSVC 使用的嵌套元素的正常(未记录)修改方案。也就是说,这只是编译器命名符号的方式。`2'

关于修改方案,请参阅例如此链接以获取更多信息。 数字本身来自 中的部分。它是一个“编码数字”。在这里,残缺名称中的数字 1 变成了 2(见此处)。另外,请参阅 此处 了解 LLVM 解构器中的相应代码。`2'?1??R<lambda_1>@?1??foo@@YAXXZ@QEBA@XZ

函数级块以损坏的数字(解构)开头,而不是因为在特殊函数中可以有更高级别的块,即构造函数中的初始值设定项列表。例如 (godbolt):1`2'0

struct Struct{
    void (*func)();
    Struct(): func(
      // ?<lambda_invoker_cdecl>@<lambda_1_>@?0???0Struct@@QEAA@XZ@SA@XZ 
      // => `Struct::Struct'::`1'::<lambda_1_>::<lambda_invoker_cdecl>
      [](){ int i = 0; }
    ) {}
};
void foo() {
  Struct s;
}

在这里,你会得到一个残缺不全的,因此是一个解缠的. 此外,在功能尝试块中,可能会出现损坏的级别。例如 (godbolt):0`1'0

void foo() try
{
}
catch(...)
{
    [](){ int i = 111; }();
}

此处的反汇编包含符号 (demangled ) 来表示 .然而,其中的 lambda 具有残缺的嵌套级别 (deangled )。(我猜 +1 用于函数体,+1 用于 catch 声明器。?catch$0@?0??foo@@YAXXZ@4HAint `void __cdecl foo(void)'::`1'::catch$0catchcatch3`4'