Eigen返回Ref引发stack-use-after-scope问题的原因探究
Eigen中Ref返回值链式调用触发stack-use-after-scope的原因
先看问题代码:
#include <Eigen/Core> using namespace Eigen; struct Z { MatrixXd q; Ref<VectorXd> foo() { return q.col(0); } auto faa() { return q.col(0); } }; int main() { Z z; z.q.resize(10, 10); z.q.setOnes(); VectorXd v(5); const auto qq = z.foo().tail(5); // 错误根源 // 以下写法可行 // const auto foo = z.foo(); // const auto qq = foo.tail(5); // // 这种写法也可行 // const auto qq = z.faa().tail(5); v = qq; return 0; }
用-fsanitize=address编译运行时,代码会在v = qq的赋值步骤触发stack-use-after-scope错误,根源在于qq存储了由foo()返回的临时Ref派生的表达式。
核心原因解析
Eigen的表达式模板机制中,多数返回的表达式对象会直接持有底层数据的指针/引用及元信息(步长、边界等),只要底层数据存活,派生表达式就能正常工作,但Ref是特殊情况:
foo()中return q.col(0)时,q.col(0)返回的是一个临时的Block表达式对象,Ref<VectorXd>的构造并不会复制这个临时对象的数据,而是持有对它的引用。- 执行
z.foo().tail(5)时,z.foo()返回的Ref对象本身也是临时的,它所引用的临时Block对象会在这条语句的表达式求值结束后被销毁。而.tail(5)返回的派生表达式(Block<Ref<VectorXd>>)会持有对这个临时Ref的引用。 - 语句结束后,临时的
Ref和它引用的Block都已超出作用域被销毁,此时qq存储的派生表达式就指向了已失效的栈对象,后续赋值时自然触发内存错误。
两种可行写法的原理
- 保存Ref到具名变量:
const auto foo = z.foo();将Ref对象存储到具名变量中,延长了它的生命周期。此时foo.tail(5)的派生表达式引用的是存活的foo对象,能正常访问底层的z.q数据。 - 直接返回表达式对象:
faa()用auto返回q.col(0)的Block表达式本身,这个Block直接持有z.q的指针和元信息,没有中间的Ref包装。.tail(5)的派生表达式也直接绑定到底层数据,只要z.q存活就不会出现悬空引用。
关于猜测的验证
你的猜测是正确的:Ref在这里确实“隐藏”了一个需要保持存活的临时表达式对象。Ref的设计目标是提供统一接口来引用不同类型的Eigen对象(直接矩阵/向量或块表达式),但当它包装临时表达式时,必须确保Ref自身的生命周期覆盖所有依赖它的派生表达式,否则就会出现悬空引用问题。
内容的提问来源于stack exchange,提问作者Svalorzen
相关产品推荐
相关产品推荐

