C++ Lambda的物理存储位置究竟在哪里?
这是《C++ Lambda的生命周期是否有限?》的后续问题。
在之前的问题中,我尝试在Lambda超出作用域后使用它导致程序崩溃。这虽不意外,但我意识到自己对Lambda存储位置的认知有误,曾以为它会一直存在,即便超出作用域。
可能因为我常使用单片机,其代码在非易失性FLASH内存中执行,与栈等数据内存完全分离。我曾以为创建Lambda等同于编写独立函数,只是语法更便捷,例如:
void SaySomething() { cout << "Hello" << endl; } int main() { AcceptFunction([](){cout << "Hello" << endl;}); // 我曾认为这和下面等价 AcceptFunction(SaySomething); // 这行代码是一样的 }
我原以为Lambda会被编译为静态存在的代码,与其他非Lambda代码共存,AcceptFunction会被传入指向该代码的指针,如此代码会一直存在于内存中,即便Lambda超出作用域。
但程序崩溃说明这不对,那Lambda的代码到底存在哪里?是在栈上吗?若是,在无法从栈中执行代码的FLASH内存架构中,行为会有不同吗?
更新
需说明的是,本问题关注的是Lambda的物理存储位置,并非希望改变其行为或阻止调用析构函数。
核心结论:Lambda的代码和普通函数一样在只读存储区(如单片机的FLASH),崩溃和代码存储无关,问题出在Lambda捕获的闭包对象上
Lambda代码的存储位置
Lambda的可执行代码部分,和普通函数、全局函数一样,会被编译器放在只读代码段(比如桌面系统的.text段,单片机的FLASH)。这部分代码是静态存在的,不会因为作用域结束而被销毁。你例子里的无捕获Lambda,本质上确实可以被隐式转换为函数指针,此时传给AcceptFunction的就是指向这段静态代码的指针,这种情况下即便Lambda所在作用域结束,调用函数指针也不会崩溃。崩溃的真正原因
你之前遇到的崩溃,大概率是因为使用了有捕获的Lambda,并且把Lambda对象(闭包)存储下来,在其作用域结束后再调用。Lambda闭包对象是一个匿名的类实例,它的存储位置取决于定义它的上下文:- 如果在栈上定义(比如局部变量),闭包对象就存在栈上,作用域结束后会被销毁;
- 如果用
std::function存储并捕获了栈上的变量,或者直接保存闭包对象的副本/引用,当原闭包销毁后,调用时访问已释放的捕获变量就会导致崩溃。
单片机架构下的特殊情况
在无法从栈执行代码的架构(如多数单片机)中,Lambda的代码依然会被放在FLASH里——因为代码本身不可能在栈上(栈是数据区,不能执行代码)。区别仅在于闭包对象的存储:如果闭包是栈上的局部对象,它的捕获数据在栈上,但执行逻辑还是在FLASH的代码段。只要你不保存闭包对象的引用/指针到其作用域之外,或者捕获的是全局/静态变量,就不会有问题。无捕获Lambda vs 普通函数
你例子里的无捕获Lambda,确实和普通函数SaySomething在调用层面等价——编译器会生成一个静态的函数体,Lambda可以隐式转为对应签名的函数指针。这种情况下,传给AcceptFunction的是代码指针,不存在作用域问题。但如果Lambda有捕获,就无法转为函数指针,必须作为闭包对象传递,此时对象的生命周期就会影响后续调用。
内容的提问来源于stack exchange,提问作者Rocketmagnet

