为何Eigen中使用负索引数组索引不会触发断言错误?
Eigen数组负索引行为疑问与解答
我正在将一个Python算法移植到C++,该算法使用一个数组对另一个数组进行索引。在Python算法中,索引数组中的-1值用作标记表示无效数据。我原本以为在Eigen中这会触发断言错误(就像直接使用越界索引访问数组时那样),但令人惊讶的是并没有。
示例代码
Eigen::Array4i values; values << 0, 1, 2, 3; Eigen::Array4i values2; values2 << 0, 1, 2, 3; Eigen::Array4i indexes; indexes << -1, -2, -1, -2; std::cout << values(indexes) << "\n\n"; std::cout << values2(indexes) << "\n\n"; // std::cout << values(-1); //eigen_assert(index >= 0 && index < size()) fails.
输出结果(调试模式和-O3编译下均如此)
0 1 0 1 3 2 3 2
显然这和Python中的行为不同(并不意外),且结果不一致让我认为这可能是返回垃圾值的未定义行为。由此我产生以下问题:
- 为何这种情况不会触发断言错误?
- 这种返回(可能是垃圾值)而非触发错误或段错误的行为是否可靠?在我的程序中,-1值已表示无效数据,我并不关心索引返回的结果,之后会将其屏蔽。
我使用的是Eigen 3.4.0和C++17。
解答
问题1:为何这种情况不会触发断言错误?
Eigen中**单个标量索引(如values(-1))和数组式索引(如values(indexes))**的边界检查逻辑是分离的:
- 单个标量索引的访问会直接触发
eigen_assert断言,因为这属于最直观的越界访问场景,Eigen会明确校验索引的合法性。 - 而数组式索引的实现中,Eigen并没有对每个索引元素做范围断言。这是因为数组式索引通常用于批量操作,逐元素校验会带来额外的性能开销,Eigen的设计优先保证数值计算的高效性,把边界检查的责任更多交给开发者。
问题2:这种返回值是否可靠?
不可靠,这属于未定义行为。
- 负索引在底层会被转换为无符号整数(因为Eigen内部存储数组的内存是连续的,索引会被当作内存偏移的计算依据),负数值转无符号后会变成一个很大的正数,导致访问数组内存范围外的区域。
- 你看到的“看似有规律”的输出只是内存中的随机值,不同环境、不同编译选项下结果可能完全不同,甚至可能触发段错误导致程序崩溃。
- 即使你之后会屏蔽这些无效值,未定义行为也可能引发不可预知的副作用(比如破坏其他变量的内存),绝对不能依赖这种行为。
推荐处理方式
在使用数组索引前,先手动过滤掉负索引:
// 将负索引替换为合法范围内的任意值(比如0),之后再屏蔽无效结果 Eigen::Array4i valid_indexes = indexes.max(0); auto result = values(valid_indexes); // 后续根据原indexes中的-1标记,屏蔽对应位置的结果
内容的提问来源于stack exchange,提问作者Mecha Wailmer
相关产品推荐
相关产品推荐

