C/C++故意超出范围索引

Dae*_*pha 6 c c++ arrays

说我有一个像这样的数组:

int val[10];
Run Code Online (Sandbox Code Playgroud)

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

我的问题是:

  1. 这样做是否安全,或者我是否会遇到某种内存保护障碍,有可能破坏某些指数的内存或类似情况?
  2. 如果我像这样访问超出范围的数据,它可能没有效率吗?(假设数组没有内置范围检查).
  3. 这会被认为是不好的做法吗?(假设写了一条注释,表示我们知道使用超出范围的索引).

ver*_*ose 17

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

  • 它也可以激发WWIII,所以我会避免未定义的行为;) (6认同)

Mat*_* M. 10

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

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

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;
}
Run Code Online (Sandbox Code Playgroud)

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

  • 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];
}
Run Code Online (Sandbox Code Playgroud)

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

编辑:

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

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


Cas*_*eri 5

正如详细 说的那样,这会产生不确定的行为.接下来会更加精确.

5.2.1/1说

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

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

然后,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];
Run Code Online (Sandbox Code Playgroud)

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

我发现这两个引用表明这个问题有多模糊:

我可以拿一个数组的一个接一个元素的地址吗?

通过C++标准的下标:legal来获取一个过去的数组元素的地址?