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

时间:2013-09-13 08:37:37

标签: c++ c arrays

说我有一个这样的数组:

int val[10];

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

我的问题是:

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

4 个答案:

答案 0 :(得分:17)

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

答案 1 :(得分:9)

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

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

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];
}

有关开发人员未采取任何禁止行为的事实,有关假设向前和向后传播的更多有趣故事,请参阅What every C programmer should know about Undefined Behavior: Part 2

修改

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

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

答案 2 :(得分:5)

作为verbose said,这会产生未定义的行为。接下来会更加精确。

5.2.1 / 1说

  
    

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

  

因此,val[i]相当于*((val)+i))。由于val是一个数组,因此在执行加法之前会发生数组到指针的转换(4.2 / 1)。因此,val[i]相当于*(ptr + i),其中ptrint*设置为&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];

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

我发现这两个参考文献显示了这个问题有多模糊:

May I take the address of the one-past-the-end element of an array?

Take the address of a one-past-the-end array element via subscript: legal by the C++ Standard or not?

答案 3 :(得分:3)

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

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];
}