我想知道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]”。
答案 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,对象的类型为元素类型。
* 您使用的系统可能会将指针中的地址表示为简单整数,而加法和减法只会调用简单的算术。可能还有系统检查加载到寄存器中的指针值。