指针算术:没有解除引用的超出范围

时间:2014-10-29 09:26:22

标签: c++ arrays language-lawyer

我想知道C ++标准是否接受以下代码。

int n{ 10 };
double* p = new double[0];
double* q = p + n;
std::cout << "n = " << static_cast<int>(q - p) << std::endl;

我希望该程序显示n的值。

由于这个问题可能看起来很奇怪,这里是对这个问题起源的解释。我想在2D中设计一个动态数组类(想想一个容器的std :: vector类,但在2D而不是1D)。一个简单的方法是:

template <typename T>
class Array2D<T> {
private:
    T* data_;
    int nb_rows_;
    int nb_columns_;
public:
    ...
};

不幸的是,这种设计不像SIMD那样友好,比如

Array2D<int> A(5, 6);
for (int i = 0; i < A.nb_rows(); ++i) {
    for (int j = 0; j < A.nb_columns(); ++j) {
        A(i, j) += 1;
    }
}
由于指针别名,编译器无法确定循环期间nb_columns_是否未更改,因此

无法进行向量化。因此,我使用与std :: vector的大多数实现相同的设计,其中向量的大小在指针中“隐藏”。

template <typename T>
class Array2D<T> {
private:
    T* data_;
    T* nb_rows_;
    T* nb_columns_;
public:
    Array2D(int n, int p) {
        data_ = new T[n * p];
        nb_rows_ = data_ + n;
        nb_columns_ = data_ + p;
    }
    ...
    int nb_columns() const {
        return static_cast<int>(nb_columns_ - data_);
    }
    ...
};

只要n> = 1且p> = 1,这种设计就可以正常工作。但是如果n = 0且p = 5,你最终会得到上面解释的那种“问题”。构造一个0行的Array2D可能很有用,因为我的类

中有以下方法
void push_back(const Array1D<T>& B);

采用大小为p的Array1D(使用断言检查)并向我的Array2D对象添加一行。你可以这样做:

Array2D<double> A(0, 10);
Array1D<double> B(10);

// work with B
A.push_back(B);

代码在clang,g ++和icpc上工作正常,但我仍然想知道它是否有效。 C ++ 11标准的5.7节是关于这个问题但是谈论“数组对象”。我想知道我的p是否指向他们所谓的“数组对象”,或者数组对象是否为“double p [5]”。

2 个答案:

答案 0 :(得分:4)

这是未定义的行为。在实践中,它可能适用于大多数现代系统,但过去曾存在导致程序崩溃的系统。指针不是只是一种特殊类型的整数;它可以有各种各样的结构,只需将指向未映射的内存的指针加载到寄存器中就可能导致陷阱。

从标准(强调添加),§5.7/ 5:

  

将具有整数类型的表达式添加到或时   从指针中减去,结果具有类型   指针操作数。如果指针操作数指向的元素   一个数组对象,和数组足够大,结果   指向偏离原始元素的元素,使得   结果和原始的下标的差异   数组元素等于整数表达式。换一种说法,   如果表达式P指向数组的第i个元素   对象,表达式(P)+ N(等效地,N +(P))和(P)-N   (其中N具有值n)分别指向第i + n个   和数组对象的第i个元素,只要它们存在。   而且,如果表达式P指向一个的最后一个元素   数组对象,表达式(P)+1点过去的一个   数组对象的元素,如果表达式Q指向一个   超过数组对象的最后一个元素,表达式(Q)-1   指向数组对象的最后一个元素。 如果两者都有   指针操作数和结果指向相同的元素   数组对象,或者超过数组对象的最后一个元素,   评估不得产生溢出;否则,   行为未定义。

最后一句话是重要的一句:“否则, 行为未定义“。

答案 1 :(得分:3)

double* q = p + n;

此行会导致未定义的行为。 [expr.add] / 5指定

  

当向指针添加或从指针中减去具有整数类型的表达式时,结果具有类型   指针操作数。 [...]如果指针操作数和结果都指向了元素   相同的数组对象,或者超过数组对象的最后一个元素,   评估不得产生溢出;否则,行为   未定义。

结果并未指向p的元素,因此最后一句适用。它肯定适用于你在*上测试它的任何系统,但它并没有以任何方式涵盖在标准中。


  

不幸的是,标准似乎没有定义什么是&#34;数组   对象&#34;是

数组对象只是一个具有数组类型的对象,简而言之:一个数组。

int a[5];

声明一个包含五个元素和大小sizeof int * 5的数组(对象)。还要考虑[expr.add] / 4:

  

出于这些运算符的目的,指向非阵列对象的指针   行为与指向数组的第一个元素的指针相同   长度为1,对象的类型为元素类型。

* 您使用的系统可能会将指针中的地址表示为简单整数,而加法和减法只会调用简单的算术。可能还有系统检查加载到寄存器中的指针值。