Eigen中auto存储四元数惰性运算表达式调用eval时访问临时对象崩溃
Eigen四元数运算表达式模板引发悬空内存访问崩溃
问题复现代码
const Eigen::Vector3d position = m_rigidBody->pos().segment<3>(0); // pos() 返回 const Eigen::VectorXd& 类型 const Eigen::Quaterniond &rotation = m_rigidBody->rot(); const auto p = rotation * m_r + position; // 使用auto接收惰性求值表达式 const auto skew = [](const Eigen::Vector3d &v) -> Eigen::Matrix3d { return Eigen::Matrix3d{{0, -v[2], v[1]}, {v[2], 0, -v[0]}, {-v[1], v[0], 0}}; }; MapType j = MapType::Zero(); j.block<3, 3>(0, 0).setIdentity(); j.block<3, 3>(0, 3) = skew((-(p - position)).eval()); // 此处触发崩溃,报非法内存访问错误 return j;
问题根因
- 崩溃的核心原因是跨语句使用
auto持有Eigen几何模块的惰性求值表达式时,表达式树内部出现了悬空引用,你之前认为所有依赖值都存活的判断,对四元数相关的表达式不成立:- 用
const auto接收运算结果时不会触发Eigen的即时求值,p本质是嵌套的表达式模板对象,不是存储实际数值的Eigen::Vector3d,整个表达式树默认按引用绑定依赖的操作数。 - Eigen 3.4之前的版本中,四元数乘向量返回的
QuaternionRotateVectorReturnType存在实现缺陷:当这个旋转表达式作为子节点被更长的链式运算表达式(比如你写的rotation * m_r + position)拷贝存储时,内部持有的四元数引用会意外悬空,后续调用eval()触发整棵表达式树求值时,就会访问到已经无效的内存地址。 - 普通矩阵乘向量返回的表达式没有这个实现缺陷,因此把四元数乘法换成旋转矩阵乘法后,代码可以正常运行。
- 用
修复方法
- 最稳妥的方案:不要用
auto跨语句接收Eigen几何模块(四元数、旋转变换、位姿变换等)的运算结果,直接显式声明值类型触发即时求值,把运算结果落地为实际的数值对象即可:// 显式指定类型为Vector3d,此处直接完成求值,p存储实际计算得到的向量值 const Eigen::Vector3d p = rotation * m_r + position; - 修改后原有
(-(p - position)).eval()的逻辑不需要任何调整,就能正常运行。 - 即使用了修复该bug的高版本Eigen,也不建议跨语句用
auto持有Eigen表达式模板:表达式模板的设计初衷是优化单条链式语句内的临时运算效率,不是用来做跨语句的变量存储,随意使用很容易踩生命周期的坑。
内容的提问来源于stack exchange,提问作者jjcasmar
相关产品推荐
相关产品推荐

