使用捕获this的Lambda初始化结构体std::function成员在Clang中触发段错误
问题描述
用捕获this的Lambda初始化结构体的std::function<int()>成员,程序预期输出9,但使用Clang 17.0.1编译(编译命令clang++ prog.cc)后运行出现Segmentation fault,而GCC编译运行无异常。编译时还会触发栈帧过大的警告:
prog.cc:11:5: warning: stack frame size (1099511627768) exceeds limit (4294967295) in 'main' [-Wframe-larger-than]
11 | int main() {
| ^
In file included from prog.cc:4:
In file included from /opt/wandbox/clang-17.0.1/include/c++/v1/functional:529:
/opt/wandbox/clang-17.0.1/include/c++/v1/__functional/function.h:310:70: warning: stack frame size (1099511627768) exceeds limit (4294967295) in 'std::__1::__function::__func<LonghistBackend::f::'lambda'(), std::__1::allocatorLonghistBackend::f:'lambda'(), int ()>::__clone() const' [-Wframe-larger-than]
310 | _LIBCPP_HIDE_FROM_ABI_VIRTUAL virtual __base<_Rp(_ArgTypes...)>* __clone() const;
| ^
问题代码:
#include <iostream> #include <memory> #include <vector> #include <functional> struct Foo { int num = 0; std::function<int()> f = [this] { return num; }; }; int main() { Foo b { 9 }; std::cout << b.f() << std::endl; return 0; }
问题原因
这是Clang配套的libc标准库的实现bug:
当在类成员的默认初始化中使用捕获this的Lambda来初始化std::function时,libc在实例化std::function内部的__clone函数模板时,错误地计算了栈帧大小,得到了一个异常巨大的数值(警告中的1099511627768字节是整数溢出导致的错误值),最终导致运行时栈溢出或非法内存访问,触发段错误。
而GCC的libstdc++标准库对这种场景的Lambda捕获和std::function初始化逻辑不同,避开了这个模板实例化的bug,因此可以正常运行。
注:C标准允许在成员默认初始化中使用this指针(此时对象处于构造过程中,但成员本身已开始初始化),所以问题本质是libc的实现缺陷,而非标准本身的问题。
解决思路
1. 移到构造函数中初始化std::function
不在类成员声明时做默认初始化,而是在构造函数的成员初始化列表或函数体内完成std::function的赋值。此时对象构造进入构造函数阶段,this指针的使用逻辑更清晰,同时避开libc++的模板bug:
struct Foo { int num = 0; std::function<int()> f; Foo(int n = 0) : num(n) { f = [this] { return num; }; } };
2. 直接捕获成员变量而非this
如果场景允许,可以直接捕获num的引用(而非this),不过要注意对象拷贝时,std::function内的引用会指向原对象的num,需要谨慎处理生命周期:
struct Foo { int num = 0; std::function<int()> f = [&num] { return num; }; };
3. 升级Clang/libc++版本
这个bug大概率是特定版本libc++的问题,升级到更新的Clang版本(如Clang 18及以上)可能已经修复了该栈帧计算错误。
内容的提问来源于stack exchange,提问作者TobyShi

