将tuple<int*,int,int,int*>转换为tuple<void*,int,int,void*>引发ASAN栈越界问题
问题重现
以下代码调用f()时,会被ASAN检测到STACK USE AFTER SCOPE错误,触发点在Data::execute()访问m_tupleData的位置:
#include <iostream> #include <tuple> using TupleData = std::tuple<void *, int, int, void *>; namespace { int x = 1, y = 2, z = 3, v = 4; // 文件作用域变量 } struct Data { Data(const TupleData & tupleData) : m_tupleData(tupleData) {} const TupleData & m_tupleData; void execute() { void * int1 = std::get<0>(m_tupleData); // ASAN在此处检测到栈越界 } }; void f() { Data data(std::make_tuple(&x, y, z, &v)); data.execute(); } int main() { f(); return 0; }
尝试过程
- 先创建元组再传入,问题依旧(注:原代码遗漏逗号,此处已修正):
void f() { auto tupleData = {&x, y, z, &v}; Data data(tupleData); data.execute(); }
- 显式声明元组为
TupleData类型后,问题解决:
void f() { TupleData tupleData = {&x, y, z, &v}; Data data(tupleData); data.execute(); }
问题原因
核心问题在于临时对象的生命周期与悬空引用:
用
std::make_tuple(&x, y, z, &v)时,返回的是std::tuple<int*, int, int, int*>类型对象。而Data构造函数接受的是const TupleData&(即std::tuple<void*, int, int, void*>的引用),编译器会自动创建一个临时的TupleData对象——通过int*类型元组隐式转换而来(int*转void*确实合法)。但C++中,绑定到函数参数的临时对象,生命周期仅延续到当前表达式结束。也就是
Data data(...)执行完后,这个临时TupleData对象就会被销毁,而Data的m_tupleData引用的正是已销毁的对象。后续调用data.execute()时访问悬空引用指向的内存,就触发了ASAN的栈越界检测。用
auto tupleData = {&x, y, z, &v}时,auto推导类型为std::initializer_list,传给Data构造函数时同样会构造临时TupleData对象,最终还是出现悬空引用问题。显式声明
TupleData tupleData = {&x, y, z, &v}时,tupleData是f()栈上的局部对象,生命周期和data一致,直到f()执行完毕才销毁。此时m_tupleData引用的是存活对象,访问不会触发越界错误。
内容的提问来源于stack exchange,提问作者Hussein Jaber

