繁体   English   中英

什么是C ++ lambda函数的默认调用约定?

[英]What's the default calling convention of a C++ lambda function?

以下代码是使用VC ++ 2012编译的:

void f1(void (__stdcall *)())
{}

void f2(void (__cdecl *)())
{}

void __cdecl h1()
{}

void __stdcall h2()
{}

int main()
{
    f1(h1); // error C2664
    f2(h2); // error C2664

    f1([](){}); // OK
    f2([](){}); // OK

    auto fn = [](){};

    f1(fn); // OK
    f2(fn); // OK
}

我认为错误是正常的,但OK可以是异常的。

所以,我的问题是:

  1. 什么是C ++ lambda函数的调用约定?

  2. 如何指定C ++ lambda函数的调用约定?

  3. 如果没有定义调用约定,那么在调用lambda函数后如何正确地回收堆栈空间?

  4. 编译器是否自动生成lambda函数的多个版本? 即作为以下伪代码:

    [] __stdcall(){};

    [] __cdecl(){}; 等等

在VC ++ 2012上, 您将“无状态lambda转换为函数指针” ,编译器会选择自动调用无状态lambdas(没有捕获变量)的转换。

MSDN C ++ 11特点

Lambda表达式

[...]此外,在Visual Studio 2012中的Visual C ++中,无状态lambda可以转换为函数指针。 [...](Visual Studio 2012中的Visual C ++甚至比这更好,因为我们已经将无状态lambda转换为具有任意调用约定的函数指针。当你使用期望类似__stdcall函数的API时,这很重要指针。)


编辑:

注意:调用转换超出了C ++标准,它依赖于其他规范,如平台ABI(应用程序二进制接口)。

以下答案基于带有/ FAs编译器选项的输出汇编代码。 所以这只是一个猜测,请向微软询问更多细节; P

Q1。 什么是C ++ lambda函数的调用约定?

Q3。 如果没有定义调用约定,那么在调用lambda函数后如何正确地回收堆栈空间?

首先,C ++ lambda(-expression)不是函数(也不是函数指针),你可以像调用普通函数一样调用operator()到lambda对象。 输出汇编代码表示VC ++ 2012使用__thiscall调用转换生成lambda-body。

Q2。 如何指定C ++ lambda函数的调用约定?

AFAIK,没有办法。 (可能只是__thiscall

Q4。 编译器是否自动生成lambda函数的多个版本? 即作为以下伪代码:[...]

可能不会.VC ++ 2012 lambda类型只提供一个lambda-body实现( void operator()() ),但是为每个调用转换提供了多个“用户定义的转换到函数指针”(操作符返回函数指针带有void (__fastcall*)(void)void (__stdcall*)(void)void (__cdecl*)(void)类型)。

这是一个例子;

// input source code
auto lm = [](){ /*lambda-body*/ };

// reversed C++ code from VC++2012 output assembly code
class lambda_UNIQUE_HASH {
  void __thiscall operator()() {
    /* lambda-body */
  }
  // user-defined conversions
  typedef void (__fastcall * fp_fastcall_t)();
  typedef void (__stdcall * fp_stdcall_t)();
  typedef void (__cdecl * fp_cdecl_t)();
  operator fp_fastcall_t() { ... }
  operator fp_stdcall_t() { ... }
  operator fp_cdecl_t() { ... }
};
lambda_UNIQUE_HASH lm;

无状态lambda函数仍然是一个类,但是可以隐式转换为函数指针的类。

C ++标准不包括调用约定,但是没有理由说无状态lambda无法在任何调用约定中创建包装器,当lambda转换为函数指针时,该约定转发到无状态lambda。

举个例子,我们可以这样做:

#include <iostream>

void __cdecl h1() {}
void __stdcall h2(){}

// I'm lazy: 
typedef decltype(&h1) cdecl_nullary_ptr;
typedef decltype(&h2) stdcall_nullary_ptr;

template<typename StatelessNullaryFunctor>
struct make_cdecl {
  static void __cdecl do_it() {
    StatelessNullaryFunctor()();
  }
};
template<typename StatelessNullaryFunctor>
struct make_stdcall {
  static void __stdcall do_it() {
    StatelessNullaryFunctor()();
  }
};

struct test {
  void operator()() const { hidden_implementation(); }

  operator cdecl_nullary_ptr() const {
    return &make_cdecl<test>::do_it;
  }
  operator stdcall_nullary_ptr() const {
    return &make_stdcall<test>::do_it;
  }
};

其中我们的test无状态nullary类可以隐式转换为cdeclstdcall函数指针。

这一点的重要部分是调用约定是函数指针类型的一部分,因此operator function_type知道正在请求什么调用约定。 并且通过完美的转发,上述甚至可以是高效的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM