文章目录
开始
本章内容需要以下几篇内容作为基础:
《c++,从汇编角度看lambda》
《c++,stl库阅读之std::forword完美转发》
std::function 是什么魔法?
它是一个类吗,类里有什么?
是一个编译器对函数指针的“别名”吗,只是概念的东西吗?
示例代码:
#include <iostream>
#include <functional>
typedef int (*func)();
void callFunc(std::function<int()>&& f) {
f();
}
int main(int args, char* argv[]) {
int a = 1, b = 2, c = 3;
callFunc(std::function<int()>([a, b, c]() -> int {
std::cout << b << c << std::endl;
return a;
}));
}
汇编中无关内容有省略
模板中的完美转发部分省略。完美转发被编译为函数,但对应gcc实现完美转发而言,无论是左值引用还是右值引用都是指针没有区别,所以完美转发函数的内容一般是,传参什么,返回什么。。。
main
先看main
函数的汇编
000000000040120a <main>:
int main(int args, char* argv[]) {
# 申请0x58大小的栈空间,存储 a b c 三个变量和 function 类
40120f: 48 83 ec 58 sub $0x58,%rsp
# -0x28(%rbp) 开始的空间里,把 abc 三个变量即立即数 1 2 3 放进去
int a = 1, b = 2, c = 3;
40121a: c7 45 ec 01 00 00 00 movl $0x1,-0x14(%rbp)
401221: c7 45 e8 02 00 00 00 movl $0x2,-0x18(%rbp)
401228: c7 45 e4 03 00 00 00 movl $0x3,-0x1c(%rbp)
callFunc(std::function<int()>([a, b, c]() -> int {
40122f: 8b 45 ec mov -0x14(%rbp),%eax
401232: 89 45 d8 mov %eax,-0x28(%rbp)
401235: 8b 45 e8 mov -0x18(%rbp),%eax
401238: 89 45 dc mov %eax,-0x24(%rbp)
40123b: 8b 45 e4 mov -0x1c(%rbp),%eax
40123e: 89 45 e0 mov %eax,-0x20(%rbp)
# 调用function的构造,传递的this是当前栈空间地址 -0x50(%rbp)
# 第二个参数是 -0x28(%rbp),即,捕获的 a b c,排排站的可整体空间的地址!
401241: 48 8d 55 d8 lea -0x28(%rbp),%rdx
401245: 48 8d 45 b0 lea -0x50(%rbp),%rax
401249: 48 89 d6 mov %rdx,%rsi
40124c: 48 89 c7 mov %rax,%rdi
40124f: e8 40 00 00 00 call 401294 <std::function<int ()>::function<main::{lambda()#1}, void>(main::{lambda()#1}&&)>
从main
函数得知,function
是一个类
奇葩的是,function的构造,传递的却是一个所有捕获变量的空间地址,却不是lambda函数地址。。。
function 右值引用函数构造
先看以下main
函数调用的function
右值引用函数构造源码:
using _Handler
= _Function_handler<_Res(_ArgTypes...), __decay_t<_Functor>>;
// _GLIBCXX_RESOLVE_LIB_DEFECTS
// 2774. std::function construction vs assignment
template<typename _Functor,
typename _Constraints = _Requires<_Callable<_Functor>>>
function(_Functor&& __f)
noexcept(_Handler<_Functor>::template _S_nothrow_init<_Functor>())
: _Function_base()
{
using _My_handler = _Handler<_Functor>;
if (_My_handler::_M_not_empty_function(__f))
{
// 上面密密麻麻,关键就在这里三行,等一下汇编看这一行
_My_handler::_M_init_functor(_M_functor,
std::forward<_Functor>(__f));
// 这两行只是保存以下地址
_M_invoker = &_My_handler::_M_invoke;
_M_manager = &_My_handler::_M_manager;
}
}
看一下汇编:
0000000000401294 <std::function<int ()>::function<main::{lambda()#1}, void>(main::{lambda()#1}&&)>:
*/
// _GLIBCXX_RESOLVE_LIB_DEFECTS
// 2774. std::function construction vs assignment
template<typename _Functor,
typename _Constraints = _Requires<_Callable<_Functor>>>
function(_Functor&& __f)
# 这一段内容比较简单,调用std::_Function_base::_Base_manager<main::{lambda()#1}>::_M_init_functor 时候传递两个参数,一个是this,一个是function构造时候传递的捕获参数的空间地址
# 就像是 f(addr) { _M_init_functor(addr);} 一样
40129c: 48 89 7d f8 mov %rdi,-0x8(%rbp)
4012a0: 48 89 75 f0 mov %rsi,-0x10(%rbp)
4012e8: 48 8b 45 f0 mov -0x10(%rbp),%rax
4012f4: 48 89 c2 mov %rax,%rdx
4012f7: 48 8b 45 f8 mov -0x8(%rbp),%rax
4012fb: 48 89 d6 mov %rdx,%rsi
4012fe: 48 89 c7 mov %rax,%rdi
401301: e8 38 00 00 00 call 40133e <void std::_Function_base::_Base_manager<main::{lambda()#1}>::_M_init_functor<main::{lambda()#1}>(std::_Any_data&, main::{lambda()#1}&&)>
std::forward<_Functor>(__f));
# $0x40136f 函数是 std::_Function_handler<int (), main::{lambda()#1}>::_M_invoke(std::_Any_data const&)
# 注意,这个invoke 是一个专属的 lambda()# 的 invoke
_M_invoker = &_My_handler::_M_invoke;
401306: 48 8b 45 f8 mov -0x8(%rbp),%rax
40130a: 48 c7 40 18 6f 13 40 movq $0x40136f,0x18(%rax)
401311: 00
_Function_base::_M_init_functor
看以下源码:
class _Function_base
{
template<typename _Fn>
static void
_M_init_functor(_Any_data& __functor, _Fn&& __f)
noexcept(__and_<_Local_storage,
is_nothrow_constructible<_Functor, _Fn>>::value)
{
_M_create(__functor, std::forward<_Fn>(__f), _Local_storage());
}
没什么内容,正常调用_M_create
汇编一样,没什么内容
000000000040133e <void std::_Function_base::_Base_manager<main::{lambda()#1}>::_M_init_functor<main::{lambda()#1}>(std::_Any_data&, main::{lambda()#1}&&)>:
_M_init_functor(_Any_data& __functor, _Fn&& __f)
401342: 48 83 ec 10 sub $0x10,%rsp
401346: 48 89 7d f8 mov %rdi,-0x8(%rbp)
40134a: 48 89 75 f0 mov %rsi,-0x10(%rbp)
_M_create(__functor, std::forward<_Fn>(__f), _Local_storage());
40134e: 48 8b 45 f0 mov -0x10(%rbp),%rax
40135a: 48 89 c2 mov %rax,%rdx
40135d: 48 8b 45 f8 mov -0x8(%rbp),%rax
401361: 48 89 d6 mov %rdx,%rsi
401364: 48 89 c7 mov %rax,%rdi
401367: e8 c1 00 00 00 call 40142d <void std::_Function_base::_Base_manager<main::{lambda()#1}>::_M_create<main::{lambda()#1}>(std::_Any_data&, main::{lambda()#1}&&, std::integral_constant<bool, true>)>
}
_Function_base::_Base_manager::_M_create
看一下源码:
// Construct a location-invariant function object that fits within
// an _Any_data structure.
template<typename _Fn>
static void
_M_create(_Any_data& __dest, _Fn&& __f, true_type)
{
::new (__dest._M_access()) _Functor(std::forward<_Fn>(__f));
}
来啦!
调用了lambda函数(_Functor(std::forward<_Fn>(__f))的构造函数!
lambda
函数,转化为function
,编译器帮我们把lambda
变成了一个类!这个类有构造函数!
题外话:
这种情况下,fakeFunc
还是一个函数,构造function
时候传递的是函数地址,_M_create
时候不会执行构造函数
int fakeFunc() {return 0;};
std::function<int()>(fakeFunc);
回到正文,继续看汇编
000000000040142d <void std::_Function_base::_Base_manager<main::{lambda()#1}>::_M_create<main::{lambda()#1}>(std::_Any_data&, main::{lambda()#1}&&, std::integral_constant<bool, true>)>:
_M_create(_Any_data& __dest, _Fn&& __f, true_type)
40143a: 48 89 75 e0 mov %rsi,-0x20(%rbp)
40145a: 48 8b 45 e0 mov -0x20(%rbp),%rax
401466: 48 89 c6 mov %rax,%rsi
40146c: e8 99 ff ff ff call 40140a <main::{lambda()#1}::main({lambda()#1}&&)>
其他都不重要了,仅仅是正常调用lambda
的构造,传递的是捕获参数的空间地址
lambda::lambda
直接看汇编,因为,没有源码。。。
000000000040140a <main::{lambda()#1}::main({lambda()#1}&&)>:
callFunc(std::function<int()>([a, b, c]() -> int {
40140a: 55 push %rbp
40140b: 48 89 e5 mov %rsp,%rbp
40140e: 48 89 7d f8 mov %rdi,-0x8(%rbp) # lambda 类的 this
401412: 48 89 75 f0 mov %rsi,-0x10(%rbp) # rsi a,b,c 值空间地址
401416: 48 8b 45 f8 mov -0x8(%rbp),%rax
40141a: 48 8b 55 f0 mov -0x10(%rbp),%rdx
40141e: 48 8b 0a mov (%rdx),%rcx
401421: 48 89 08 mov %rcx,(%rax) # 从 a,b,c 值空间地址,拷贝内容到 this
401424: 8b 52 08 mov 0x8(%rdx),%edx
401427: 89 50 08 mov %edx,0x8(%rax) # 第二次拷贝,三个int类型占12字节
哇,多么朴实无华的一个类构造函数,lambda类不仅有构造函数,还有自己的空间
就像下面的伪代码一样
class lambda {
struct {int a, int b, int c} Args; // 声明捕获内容
Args args; // 捕获参数存储在类里
lambda(Args& args) { // 构造,复制捕获内容
this.args = args;
}
}
callFunc
接下来看function
如何执行的
先看callFunc
,内容朴实无华,调用function
的操作符()
函数
void callFunc(std::function<int()>&& f) {
f();
}
void callFunc(std::function<int()>&& f) {
4011b9: e8 02 06 00 00 call 4017c0 <std::function<int ()>::operator()() const>
}
std::function<int ()>::operator()
先看下源码:
_Res
operator()(_ArgTypes... __args) const
{
if (_M_empty())
__throw_bad_function_call();
return _M_invoker(_M_functor, std::forward<_ArgTypes>(__args)...);
}
执行_M_invoker
函数,这个函数正是function 右值引用函数构造
章节中保存的一个地址
// 这两行只是保存以下地址
_M_invoker = &_My_handler::_M_invoke;
当时保存在哪了呢?
# $0x40136f 函数是 std::_Function_handler<int (), main::{lambda()#1}>::_M_invoke(std::_Any_data const&)
# 注意,这个invoke 是一个专属的 lambda()# 的 invoke
_M_invoker = &_My_handler::_M_invoke;
401306: 48 8b 45 f8 mov -0x8(%rbp),%rax
40130a: 48 c7 40 18 6f 13 40 movq $0x40136f,0x18(%rax)
正是0x18(%rax)
和下面汇编的rdx
位置对应
00000000004017c0 <std::function<int ()>::operator()() const>:
4017c8: 48 89 7d f8 mov %rdi,-0x8(%rbp)
{
if (_M_empty())
4017cc: 48 8b 45 f8 mov -0x8(%rbp),%rax
4017e5: 48 8b 50 18 mov 0x18(%rax),%rdx
4017e9: 48 8b 45 f8 mov -0x8(%rbp),%rax
4017ed: 48 89 c7 mov %rax,%rdi
4017f0: ff d2 call *%rdx # rdx = this + 0x18
接下来,看当时保存的lambda::_M_invoke
函数
# readelf -s -W main |c++filt |grep 40136f
19: 000000000040136f 34 FUNC LOCAL DEFAULT 14 std::_Function_handler<int (), main::{lambda()#1}>::_M_invoke(std::_Any_data const&)
而剩下的,都是编译器生成的内容了:
lambda::_M_invoke -> lambda::operate()
_M_invoke
-> __invoke_r
-> __invoke_impl
-> lambda::operate()
中间的一段invoke
是编译器生成的,不确定有没有源码,不重要,中间都是一些完美转发、参数传递的内容
最终,调用到了lambda
类的::operate()
位置:
看一下lambda
的真正内容
callFunc(std::function<int()>([a, b, c]() -> int {
4011c6: 48 83 ec 10 sub $0x10,%rsp
4011ca: 48 89 7d f8 mov %rdi,-0x8(%rbp)
4011ce: 48 8b 45 f8 mov -0x8(%rbp),%rax # rax = this
4011d2: 8b 40 04 mov 0x4(%rax),%eax # this + 4
std::cout << b << c << std::endl;
4011d5: 89 c6 mov %eax,%esi # a = this + 4
4011d7: bf 80 40 40 00 mov $0x404080,%edi
4011dc: e8 af fe ff ff call 401090 <std::basic_ostream<char, std::char_traits<char> >::operator<<(int)@plt>
a = this + 4
,还记得吗,a,b,c
变量在lambda
构造时被复制过来了,这段operate()
非常朴实无华,使用捕获的参数,就像使用类的参数一模一样。
end
std::function
构造带捕获的lambda
时,lamda
变成了一个类,构造传参是捕获参数,std::function
构造时会执行lambda
构造函数b并会将捕获参数保存,lambda函数内容变成operator()
函数;std::function
被传入的是lambda
类的传参。
又一个非常刁钻,非常有意思的面试题出现了,lambda
匿名函数是函数吗?
考察了对lambda
捕获、std::function
的原理。