范围for循环使用返回initializer_list的三元表达式出现未定义行为
问题原因分析
std::initializer_list<T>是极轻量的包装类型,内部仅存储指向底层数组的首、尾指针(或首指针加长度),本身不持有元素的存储空间。- 当编写
X{7, 8, 2, 3, 9}这类表达式时,编译器会完成两个操作:首先在当前栈帧创建一个生命周期仅属于当前完整表达式的const int[5]临时数组存储字面量值,再创建std::initializer_list<int>临时对象指向这个栈数组。
结合范围for的标准展开规则:
// 范围for会被展开为类似如下逻辑 auto&& range = 范围表达式; auto begin = range.begin(); auto end = range.end(); for (; begin != end; ++begin) { int i = *begin; // 循环体逻辑 }
你代码中的范围表达式是三元运算(true? X{7, 8, 2, 3, 9}: X{5, 6, 7}),执行流程如下:
- 三元运算执行过程中,创建栈临时数组和对应的
initializer_list临时对象,三元运算返回该initializer_list的右值。 initializer_list临时对象直接绑定到auto&& range,所以它自身的生命周期会被延长到整个for循环结束,但它指向的底层栈数组的生命周期不会被延长:数组仅属于三元运算这个完整表达式,表达式执行完毕后栈数组就被销毁,对应内存会被后续栈操作复用。- 进入循环后访问元素本质是读取已经被销毁的栈内存,最终输出未定义的垃圾值。
std::vector行为正常的原因
std::vector<T>是独立持有元素存储空间的容器,和initializer_list的逻辑完全不同:
- 用
std::vector<int>{7, 8, 2, 3, 9}初始化时,vector会把initializer_list指向的临时数组中的元素全部拷贝到自身管理的堆内存中。 - 三元运算返回的vector临时对象直接绑定到范围for的引用,生命周期被延长到整个for循环结束,其管理的堆内存也不会提前释放,所以访问元素是安全的,可以输出预期结果。
规避方案
如果需要使用std::initializer_list作为范围for的遍历对象,需要保证其底层数组的生命周期覆盖整个循环:
#include <iostream> #include <initializer_list> using X = std::initializer_list<int>; int main() { X list1{7, 8, 2, 3, 9}, list2{5, 6, 7}; for (int i: (true ? list1 : list2)) std::cout << i << ' '; }
这种场景下数组的生命周期和list1/list2变量绑定,会持续到作用域结束,不会提前销毁。
内容的提问来源于stack exchange,提问作者GT 77
相关产品推荐
相关产品推荐

