为何添加assert()会触发GCC的maybe-uninitialized警告?
问题背景
以下代码编译时无任何警告或错误:
#include <bitset> #include <Eigen/Core> using Array = Eigen::Array<float, -1, 1, 0, 3, 1>; struct Tree { Array array; std::bitset<8> bitset; }; auto func(Tree* tree) { int c = tree->array.rows(); int d = tree->bitset.count(); //assert(c==d); Array e(c); for (int k = 0; k < d; ++k) e(k) = k; return e.sum() / (e + 1); } int main() { Tree tree; func(&tree); return 0; }
编译命令:
g++ -O3 -Wall -I anaconda3/envs/dev/include/eigen3/ test.cpp
但取消assert(c==d);的注释后,GCC会抛出maybe-uninitialized警告:
In file included from anaconda3/envs/dev/include/eigen3/Eigen/Core:253, from test.cpp:2: In member function ‘Eigen::internal::scalar_sum_op<LhsScalar, RhsScalar>::result_type Eigen::internal::scalar_sum_op<LhsScalar, RhsScalar>::operator()(const LhsScalar&, const RhsScalar&) const [with LhsScalar = float; RhsScalar = float]’, inlined from ‘static Eigen::internal::redux_impl<Func, Evaluator, 3, 0>::Scalar Eigen::internal::redux_impl<Func, Evaluator, 3, 0>::run(const Evaluator&, const Func&, const XprType&) [with XprType = Eigen::Array<float, -1, 1, 0, 3, 1>; Func = Eigen::internal::scalar_sum_op<float, float>; Evaluator = Eigen::internal::redux_evaluator<Eigen::Array<float, -1, 1, 0, 3, 1> >]’ at anaconda3/envs/dev/include/eigen3/Eigen/src/Core/Redux.h:278:19, inlined from ‘typename Eigen::internal::traits<T>::Scalar Eigen::DenseBase<Derived>::redux(const Func&) const [with BinaryOp = Eigen::internal::scalar_sum_op<float, float>; Derived = Eigen::Array<float, -1, 1, 0, 3, 1>]’ at anaconda3/envs/dev/include/eigen3/Eigen/src/Core/Redux.h:418:56, inlined from ‘typename Eigen::internal::traits<T>::Scalar Eigen::DenseBase<Derived>::sum() const [with Derived = Eigen::Array<float, -1, 1, 0, 3, 1>]’ at anaconda3/envs/dev/include/eigen3/Eigen/src/Core/Redux.h:463:25, inlined from ‘auto func(Tree*)’ at test.cpp:17:15: anaconda3/envs/dev/include/eigen3/Eigen/src/Core/functors/BinaryFunctors.h:42:122: warning: ‘((const float*)((char*)&e + offsetof(Eigen::Array, Eigen::Array<float, -1, 1, 0, 3, 1>::<unnamed>.Eigen::PlainObjectBase<Eigen::Array<float, -1, 1, 0, 3, 1> >::m_storage)))[3]’ may be used uninitialized [-Wmaybe-uninitialized] 42 | EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE result_type operator() (const LhsScalar& a, const RhsScalar& b) const { return a + b; } | ~~^~~ test.cpp: In function ‘auto func(Tree*)’: test.cpp:15:9: note: ‘e’ declared here 15 | Array e(c); | ^
核心问题:
- 为什么添加
assert(c==d)会让GCC开始报告未初始化警告? - 仅仅是比较两个整数的assert,为何会触发这类和数组初始化相关的警告?
(注:此为最小复现示例,移除for循环、修改返回表达式或替换std::bitset都会改变警告状态,环境为gcc-11.3.0和eigen-3.4.0)
解答
核心原因:assert改变了GCC的数据流分析逻辑
GCC的-Wmaybe-uninitialized警告靠追踪变量的初始化状态和代码路径约束来判断是否存在风险,assert的出现给编译器提供了关键的约束条件,让它能发现原本隐藏的风险路径。
无assert时:编译器无法确定风险路径
没有assert(c==d)时,GCC对c和d的认知是:
c来自未初始化的tree->array.rows(),值完全未知;d来自未初始化的tree->bitset.count(),值也未知;- 数组
e的逻辑大小是c,但循环只初始化前d个元素。
此时编译器无法确定c和d的大小关系:如果c<=d,所有元素都被初始化;如果c>d,才会有未初始化元素被sum()用到。但因为两个变量都是未知的,编译器没法确定是否存在这条危险路径,所以不会触发警告。
有assert时:编译器获得了约束条件
添加assert(c==d)后,GCC会将其视为一条代码路径约束——它会假设:只要代码能走到sum()那一步,就说明c==d成立。
结合你定义的Eigen数组特性来看:Array是固定最大行数为3的动态数组(模板参数MaxRows=3),Eigen会预分配能存3个float的内存,即使逻辑上的行数c小于3。在-O3优化下,Eigen的sum()实现会直接遍历整个预分配的内存块,而不是只遍历逻辑上的c个元素。
此时编译器会推断:
如果c==d且d<3,那么循环只初始化了前d个元素,预分配内存中剩下的元素是未初始化的,但sum()会访问它们。而因为d是未初始化bitset的count值,完全可能出现d<3的情况,所以编译器就触发了未初始化警告。
总结
assert并没有直接引发未初始化问题,它只是给编译器提供了足够的约束条件,让编译器能发现原本因为变量关系不明确而无法检测到的潜在风险。加上Eigen固定最大大小数组的特殊实现,最终导致了警告的出现。
内容的提问来源于stack exchange,提问作者dshin

