C/C++ 有意超出范围的索引

C/C++ intentional out of range indexing

本文关键字:范围 索引 C++      更新时间:2023-10-16

假设我有一个这样的数组:

int val[10];

我故意用从负值到高于 9 的所有值来索引它,但没有以任何方式使用结果值。这是出于性能原因(也许在进行数组访问后检查输入索引更有效)。

我的问题是:

  1. 这样做是否安全,或者我会遇到某种内存保护屏障,冒着损坏内存或某些索引类似风险的风险?
  2. 如果我像这样访问超出范围的数据,是否效率不高?(假设数组没有内置的范围检查)。
  3. 会不会算是不好的做法?(假设写了一条注释来表明我们知道使用超出范围的索引)。

这是未定义的行为。根据定义,未定义意味着"任何事情都可能发生"。你的代码可能会崩溃,它可以完美地工作,它可以带来所有人之间的和平与和谐。我不会赌第二个或最后一个。

这是未定义的行为,您实际上可能会与优化器发生冲突。

想象一下这个简单的代码示例:

int select(int i) {
    int values[10] = { .... };
    int const result = values[i];
    if (i < 0 or i > 9) throw std::out_of_range("out!");
    return result;
}

现在从优化器的角度来看:

  • int values[10] = { ... };:有效索引在[0, 9] 中。

  • values[i]i 是一个索引,因此i[0, 9] 中。

  • if (i < 0 or i > 9) throw std::out_of_range("out!");i[0, 9],从未服用

因此,优化器重写了函数:

int select(int i) {
    int values[10] = { ... };
    return values[i];
}

有关基于开发人员没有做任何被禁止的事情这一事实的假设向前和向后传播的更有趣的故事,请参阅每个 C 程序员都应该知道的关于未定义行为的知识:第 2 部分。

编辑:

可能的解决方法:如果您知道将从-M访问到+N则可以:

  • 使用适当的缓冲区声明数组:int values[M + 10 + N]
  • 偏移任何访问:values[M + i]

正如 verbose 所说,这会产生未定义的行为。接下来是更精确的一点。

5.2.1/1 说

[...]表达式 E1[E2] 与 *((E1)+(E2))相同(根据定义)

因此,val[i]等同于*((val)+i))。由于val是一个数组,因此数组到指针的转换 (4.2/1) 在执行加法之前发生。因此,val[i] 等效于*(ptr + i)其中 ptr 是设置为 &val[0]int*

然后,5.7/2 解释了ptr + i所指的内容。它还说(强调是我的):

[...]如果指针操作数结果都指向同一数组对象的元素,或者指向数组对象的最后一个元素,则计算不应产生溢出;否则,行为是未定义的

ptr + i 的情况下,ptr 是指针操作数结果ptr + i 。根据上面的引用,两者都应该指向数组中的一个元素或指向最后一个元素之后的一个元素。也就是说,在OP的情况下,ptr + i是所有i = 0, ..., 10的明确定义的表达式。最后,*(ptr + i)0 <= i < 10定义得很好,但对i = 10没有定义。

编辑

我对val[10](或等效地*(ptr + 10))是否产生未定义的行为感到困惑(我正在考虑C++而不是 C)。在某些情况下,这是正确的(例如 int x = val[10];是未定义的行为),但在其他人中,这并不那么清楚。例如

int* p = &val[10];

正如我们所看到的,这等同于int* p = &*(ptr + 10);可能是未定义的行为(因为它取消引用指向val最后一个元素的指针)或与定义良好的int* p = ptr + 10;相同。

我找到了这两个参考资料,表明这个问题是多么模糊:

我可以获取数组的"一端"元素的地址吗?

通过下标获取一个过去结束的数组元素的地址:C++标准是否合法?

如果你把它放在一个带有一些填充整数的结构中,它应该是安全的(因为指针实际上指向"已知"目的地)。但最好避免它。

struct SafeOutOfBoundsAccess
{ 
    int paddingBefore[6];
    int val[10];
    int paddingAfter[6];
};
void foo()
{
    SafeOutOfBoundsAccess a;
    bool maybeTrue1 = a.val[-1] == a.paddingBefore[5];
    bool maybeTrue2 = a.val[10] == a.paddingAfter[0];
}