C++11新特性中的匿名函数Lambda表达式的汇编实现分析(三)

简介:

Lambda表达式中较复杂的形式如下:

[ capture ]( params ) -> ret { body }

现在我们构造一个简单的Lambda闭包函数进行分析:

int main()
{
    int c = 10;
    auto lambda = [&](int a, int b)->int{
        return a + b - c;
    };
    int r = lambda(1, 2);

    return 0;
}

上面的代码中,lambda表达式要求传递两个参数a和b,并按引用捕获c,计算后的结果返回给r。

相应的汇编码如下:

int c = 10;
 mov         dword ptr [ebp-8],0Ah  
    auto lambda = [&](int a, int b)->int{
        return a + b - c;
    };
 lea         eax,[ebp-8]  
 push        eax  
 lea         ecx,[ebp-14h]  
 call        010E13B0  
    int r = lambda(1, 2);
 push        2  
 push        1  
 lea         ecx,[ebp-14h]  
 call        010E1400  
 mov         dword ptr [ebp-20h],eax  

    return 0;
 xor         eax,eax

显而易见的,和前面两篇文中的一样,这里仅作简要说明:

由于Lambda表达式中捕获了c,因此这里第一个lea指令,向复制函数传递了c的地址,第二条lea指令向复制函数传递了this用于记录捕获对象的地址,

发生调用时,两个push按照stdcall的方式,从右向左压栈。并向表达式传入了this用于寻址。

lambda调用完毕的返回值默认放在eax中,因此,这里最后一个mov意思是将闭包的函数返回值写入r中。

那么,再看看闭包内如何处理传入参数的以及如何返回的?其实就像普通函数一样的原理,以前的博文也说到过函数调用的汇编原理,这里再简单说说吧。

pop         ecx  
 mov         dword ptr [ebp-8],ecx  
        return a + b - c;
 mov         eax,dword ptr [ebp+8]  
 add         eax,dword ptr [ebp+0Ch]  
 
 mov         ecx,dword ptr [ebp-8]  
 mov         edx,dword ptr [ecx]  
 sub         eax,dword ptr [edx]
参数[ebp+8] = a ;[ebp+0Ch] = b

eax往往是放临时量,edx往往是放地址,按照这个经验,很容易看出,后面两个mov取出this(得到的是&c)然后从a+b中减去(&c),结果放在eax中,ret返回后供主函数中的r获取之。

三篇博文的总结:

C++11中lambda表达式在形式上改变了函数的书写,使函数调用更加简洁灵活,闭包函数也是许多高级语言的特性之一。

Lambda表达式并不是一个神奇的东西,万变不离其宗,他仍然是以一个函数的形式存在于汇编中,底层处理和普通函数基本一样。

Lambda表达式和普通函数在源程序的实现上有不同:

Lambda表达式通常被作为参数传递给另一个函数,它本身作为callback,以此避免在其他地方写出完整函数或使用函数指针。

Lambda表达式和普通函数在汇编层上的实现基本相同:

最特殊的地方是,当闭包中要使用本身作用域外的变量时,需要进行“捕获”,而捕获其实是通过另一个隐藏的(源代码不可见),我叫它复制函数(或者叫准备函数吧)来实现的,具体实现根据捕获方式的不同而不同,大体上是一系列赋值语句。

闭包中通过传入的this指针(不能直接使用)对捕获的变量或者对象进行操作。

关于捕获方式中的按值或者按引用的概念,和普通函数一致。

好了,说到这里,Lambda表达式的底层实现基本说到,本系列博文均属原创,感谢开源中国OSChina提供这样一个学习交流的平台,读者如有其它见解,欢迎评论!

最后送大家一句话,也是原创的。

我们不需要重复造轮子,但是我们必须具有造轮子的能力。

相关文章
|
3月前
|
编译器 C++ 开发者
C++一分钟之-C++20新特性:模块化编程
【6月更文挑战第27天】C++20引入模块化编程,缓解`#include`带来的编译时间长和头文件管理难题。模块由接口(`.cppm`)和实现(`.cpp`)组成,使用`import`导入。常见问题包括兼容性、设计不当、暴露私有细节和编译器支持。避免这些问题需分阶段迁移、合理设计、明确接口和关注编译器更新。示例展示了模块定义和使用,提升代码组织和维护性。随着编译器支持加强,模块化将成为C++标准的关键特性。
137 3
|
8天前
|
Ubuntu Linux Shell
C++ 之 perf+火焰图分析与调试
简介 在遇到一些内存异常的时候,经常这部分的代码是很难去进行分析的,最近了解到Perf这个神器,这里也展开介绍一下如何使用Perf以及如何去画火焰图。 1. Perf 基础 1.1 Perf 简介 perf是Linux下的一款性能分析工具,能够进行函数级与指令级的热点查找。利用perf剖析程序性能时,需要指定当前测试的性能时间。性能事件是指在处理器或操作系统中发生的,可能影响到程序性能的硬件事件或软件事件 1.2 Perf的安装 ubuntu 18.04: sudo apt install linux-tools-common linux-tools-4.15.0-106-gen
13 2
|
29天前
|
安全 NoSQL Redis
C++新特性-智能指针
C++新特性-智能指针
|
2月前
|
安全 编译器 C++
C++一分钟之-泛型Lambda表达式
【7月更文挑战第16天】C++14引入泛型lambda,允许lambda接受任意类型参数,如`[](auto a, auto b) { return a + b; }`。但这也带来类型推导失败、隐式转换和模板参数推导等问题。要避免这些问题,可以明确类型约束、限制隐式转换或显式指定模板参数。示例中,`safeAdd` lambda使用`static_assert`确保只对算术类型执行,展示了一种安全使用泛型lambda的方法。
33 1
|
2月前
|
数据安全/隐私保护 C++
|
3月前
|
安全 JavaScript 前端开发
C++一分钟之-C++17特性:结构化绑定
【6月更文挑战第26天】C++17引入了结构化绑定,简化了从聚合类型如`std::tuple`、`std::array`和自定义结构体中解构数据。它允许直接将复合数据类型的元素绑定到单独变量,提高代码可读性。例如,可以从`std::tuple`中直接解构并绑定到变量,无需`std::get`。结构化绑定适用于处理`std::tuple`、`std::pair`,自定义结构体,甚至在范围for循环中解构容器元素。注意,绑定顺序必须与元素顺序匹配,考虑是否使用`const`和`&`,以及谨慎处理匿名类型。通过实例展示了如何解构嵌套结构体和元组,结构化绑定提升了代码的简洁性和效率。
60 5
|
2月前
|
存储 安全 编译器
|
3月前
|
自然语言处理 C语言 C++
程序与技术分享:C++写一个简单的解析器(分析C语言)
程序与技术分享:C++写一个简单的解析器(分析C语言)
|
13天前
|
存储 编译器 C++
C ++初阶:类和对象(中)
C ++初阶:类和对象(中)
|
13天前
|
C++
C++(十六)类之间转化
在C++中,类之间的转换可以通过转换构造函数和操作符函数实现。转换构造函数是一种单参数构造函数,用于将其他类型转换为本类类型。为了防止不必要的隐式转换,可以使用`explicit`关键字来禁止这种自动转换。此外,还可以通过定义`operator`函数来进行类型转换,该函数无参数且无返回值。下面展示了如何使用这两种方式实现自定义类型的相互转换,并通过示例代码说明了`explicit`关键字的作用。