C++捕获/非捕获lambda与std::function、函数指针的sizeof差异解析
C++可调用对象的内存大小差异解析
测试代码
#include <iostream> #include <functional> std::function<int(int)> makeLambda1(int x) { return [x] (int y) { return x * y; }; } auto makeLambda2(int x) { return [x] (int y) { return x * y; }; } auto makeLambda3() { return [] (int y) { return 10 * y; }; } int main() { auto lambda1 = makeLambda1(10); auto lambda2 = makeLambda2(10); auto lambda3 = makeLambda3(); std::cout << sizeof(lambda1) << " "; std::cout << sizeof(lambda2) << " "; std::cout << sizeof(lambda3) << " "; std::cout << sizeof(int(*)(int)) << std::endl; }
运行输出
32 4 1 8
核心原理说明
1. 捕获式lambda体积远小于对应std::function的原因
- lambda的本质:编译器会为每段lambda代码生成一个唯一的匿名闭包类,lambda表达式产生的对象就是这个类的实例。类的成员变量完全对应lambda捕获的外部变量,没有冗余结构。本次测试中捕获的是单个
int类型变量,刚好占4字节,因此lambda实例的大小就是4字节,没有额外开销。 - std::function的本质:它是支持所有可调用对象(普通函数、lambda、仿函数、成员函数指针等)的通用类型擦除封装器,设计目标是一套接口兼容所有可调用类型,因此必然存在固定的结构开销:
- 预留固定大小的内部缓冲区,用来直接存储体积较小的可调用对象,避免堆分配开销
- 存储一组类型擦除的操作函数指针,负责完成内部可调用对象的调用、拷贝、销毁逻辑
- 维护自身的状态标记,判断内部存储的对象类型
这些固定结构的体积叠加后,哪怕内部存储的是4字节的小lambda,整个std::function实例的大小也会达到32字节,这是通用适配能力带来的固有体积代价。
2. 非捕获式lambda体积小于普通函数指针的原因
- 非捕获式lambda没有任何需要捕获的外部变量,对应的匿名闭包类没有任何成员变量,属于C中的空类。按照C内存规则,空类的大小不能为0——否则无法保证不同类实例的内存地址唯一,因此编译器会给空类自动加上1字节的占位,这就是其大小为1字节的来源。
- 64位系统下的普通函数指针,本身需要存储函数入口的虚拟地址,64位地址长度恰好为8字节,因此同签名的普通函数指针大小为8字节。需要特别注意的是:非捕获lambda本身的1字节只是空类的占位,内部并没有存储函数地址;只有当非捕获lambda被隐式转换为普通函数指针时,才会生成一个8字节的指针值,指向编译器为这个lambda生成的静态函数实体。
内容的提问来源于stack exchange,提问作者3CEZVQ
相关产品推荐
相关产品推荐

