2014-10-29 3 views
2

Я хотел бы знать, не принят ли следующий код стандартом 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_); 
    } 
    ... 
}; 

Эта конструкция хорошо работает до тех пор, п> = 1 и р> = 1. Но при п = 0 и р = 5, вы в конечном итоге с такой «проблемы» пояснено выше. Построение Array2D с 0 строк может быть полезным из-за следующий метод в моем классе

void push_back(const Array1D<T>& B); 

, которая принимает Array1D размера р (проверено с утверждаешь) и добавляет строку в моем объект Array2D. Вы можете сделать:

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

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

код прекрасно работает на лязг, г ++ и ICPC, но я все еще интересно, если это действительно. Раздел 5.7 стандарта C++ 11 относится к этой проблеме, но говорит о «объектах массива». Мне интересно, указывает ли мой p на то, что они называют «объектом массива», или если объект массива является чем-то вроде «double p [5]».

+1

@Joachim Pileborg: Я хотел бы быть в этом уверен. Можете ли вы привести этот стандарт в этом вопросе? Также может произойти переполнение указателя, поскольку data_ + n может переполняться. – InsideLoop

+0

Я не знаю, что будет делать с нулевым размером массива? –

+0

Как сказал Али Казми, что хорошо для массива нулевого размера? Не могли бы вы просто проверить, если n * p == 0, тогда просто возвращайтесь так: 'Array2D (int n, int p) { if (n * p == 0) return; data_ = new T [n * p]; nb_rows_ = data_ + n; nb_columns_ = data_ + p; } ' –

ответ

4

Это неопределенное поведение. На практике это, вероятно, будет работать на большинстве современных систем, но в прошлом существовали системы, в которых это могло бы привести к сбою программы. Указатель не только особый тип целого; он может иметь все виды структуры, и просто загрузка указателя на немаркированную память в регистр может вызвать ловушку.

От стандарта (подчеркивание добавлено), §5.7/5:

When an expression that has integral type is added to or subtracted from a pointer, the result has the type of the pointer operand. If the pointer operand points to an element of an array object, and the array is large enough, the result points to an element offset from the original element such that the difference of the subscripts of the resulting and original array elements equals the integral expression. In other words, if the expression P points to the i-th element of an array object, the expressions (P)+N (equivalently, N+(P)) and (P)-N (where N has the value n) point to, respectively, the i + n-th and i − n-th elements of the array object, provided they exist. Moreover, if the expression P points to the last element of an array object, the expression (P)+1 points one past the last element of the array object, and if the expression Q points one past the last element of an array object, the expression (Q)-1 points to the last element of the array object. If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined.

Это последнее предложение является важным: "в противном случае, поведение не определено".

+0

Это было частью стандарта, о котором я говорил. К сожалению, стандарт, похоже, не определяет, что такое «объект массива». Мне было интересно, было ли это только что-то вроде double p [5], а также что-то вроде double * p = new double [5]. Вы нашли что-нибудь в стандарте, определяющем, что такое «объект массива»? – InsideLoop

+1

@InsideLoop Если вы выделяете в качестве массива (например, 'new [...]'), то полученный указатель является указателем на объект массива. Кроме того, если вы объявляете массив, это тоже объект массива. Это несколько разъясняется, читая $ 5.3.4/1 стандарта C++ 11. Также см., Например, $ 18.6.1.2/1. –

+1

@InsideLoop: проверьте параграф чуть выше: «7 Для целей этих операторов указатель на объект, который не является элементом массива, ведет себя так же, как указатель на первый элемент массива длиной один с тип объекта как его тип элемента. ", поэтому в этом контексте все является массивом. –

3
double* q = p + n; 

Эта строка индуцирует неопределенное поведение. [Expr.add]/5 указывает, что

When an expression that has integral type is added to or subtracted from a pointer, the result has the type of the pointer operand. […] If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined.

Результат не указывает на элемент p, таким образом, применяется последнее предложение. Это, безусловно, будет работать в любой системе, на которой вы ее протестируете *, но никак не покрывается стандартом.


Unfortunately, the standard does not seem to define what an "array object" is.

Массив объект только объект с типом массива, коротко говоря: массив.

int a[5]; 

Объявляет массив (объект) с пятью элементами и размером sizeof int * 5. Также рассмотрит [expr.add]/4:

For the purposes of these operators, a pointer to a nonarray object behaves the same as a pointer to the first element of an array of length one with the type of the object as its element type.

* системы, которую вы используете, вероятно, представляют собой адрес указателей как простые числа и сложение и вычитание будет вызывать только простую арифметику. Могут также существовать системы, которые проверяют значения указателя, загружаемые в регистры.

Смежные вопросы