Расширение gcc в C ++ для ненулевого выделения указателей массива? - PullRequest
0 голосов
/ 01 марта 2019

Я ищу gcc-поддерживаемое расширение языка C ++, чтобы разрешить выделение ненулевых указателей на массивы.В идеале я мог бы просто написать:

#include<iostream>  
using namespace std;

// Allocate elements array[lo..hi-1], and return the new array.
template<typename Elem>
Elem* Create_Array(int lo, int hi)
{
  return new Elem[hi-lo] - lo;
  // FIXME what about [expr.add]/4.
  // How do we create a pointer outside the array bounds?
}

// Deallocate an array previously allocated via Create_Array.
template<typename Elem>
void Destroy_Array(Elem* array, int lo, int hi)
{
  delete[](array + lo);
}


int main() 
{  
  const int LO = 1000000000;
  const int HI = LO + 10;
  int* array = Create_Array<int>(LO, HI);
  for (int i=LO; i<HI; i++)
    array[i] = i;
  for (int i=LO; i<HI; i++)
    cout << array[i] << "\n";
  Destroy_Array(array, LO, HI);
} 

Приведенный выше код работает, но не определяется стандартом C ++.В частности, проблема заключается в [expr.add] / 4 :

Когда выражение с целым типом добавляется или вычитается из указателя, результат имеет типоперанд указателя.Если выражение P указывает на элемент x [i] объекта массива x с n элементами, выражения P + J и J + P (где J имеет значение j) указывают на (возможно, гипотетический) элемент x [i +j] если 0 ≤ i + j ≤ n; в противном случае поведение не определено .Аналогично, выражение P - J указывает на (возможно, гипотетический) элемент x [i - j], если 0 ≤ i - j ≤ n;в противном случае поведение не определено.

Другими словами, поведение не определено для строки, помеченной как FIXME в приведенном выше коде, поскольку оно вычисляет указатель, который находится за пределами диапазона x[0..n] для 0-основанный массив x.

Есть ли какая-либо опция --std=... для gcc, позволяющая указывать, что указатели массива, не основанные на нулях, могут быть непосредственно рассчитаны?

Если нет,Есть ли достаточно портативный способ эмулировать оператор return new Type[hi-lo] - lo;, возможно, приведение к long и обратно?(но тогда я бы беспокоился о появлении новых ошибок)

Кроме того, можно ли это сделать так, чтобы для отслеживания каждого массива требовался только 1 регистр, как в коде выше?Например, если у меня array1[i], array2[i], array3[i], для указателей массива требуется только 3 регистра array1, array2, array3, плюс один регистр для i?(аналогично, при холодной выборке ссылок на массивы, мы должны иметь возможность просто получить указатель, не основанный на нуле, без выполнения вычислений, чтобы просто установить ссылку в регистрах)

1 Ответ

0 голосов
/ 03 марта 2019

Предполагая, что вы используете gcc в linux x86-64, он поддерживает типы intptr_t и uintptr_t, которые могут содержать любое значение указателя (действительное или нет), а также поддерживают целочисленную арифметику.uintptr_t больше подходит в этом приложении, потому что оно поддерживает mod 2 ^ 64 семантику , в то время как intptr_t имеет случаи UB.

Как предлагается в комментариях, мы можем использовать это для создания классакоторый перегружает operator[] и выполняет проверку диапазона:

#include <iostream> 
#include <assert.h>
#include <sstream> // for ostringstream
#include <vector>  // out_of_range
#include <cstdint> // uintptr_t
using namespace std;


// Safe non-zero-based array. Includes bounds checking.
template<typename Elem>
class Array {
  uintptr_t array; // base value for non-zero-based access
  int       lo;    // lowest valid index
  int       hi;    // highest valid index plus 1

public:

  Array(int lo, int hi)
    : array(), lo(lo), hi(hi)
  {
    if (lo > hi)
      {
        ostringstream msg; msg<<"Array(): lo("<<lo<<") > hi("<<hi<< ")";
        throw range_error(msg.str());
      }
    static_assert(sizeof(uintptr_t) == sizeof(void*),
          "Array: uintptr_t size does not match ptr size");
    static_assert(sizeof(ptrdiff_t) == sizeof(uintptr_t),
          "Array: ptrdiff_t size does not match ptr (efficieny issue)");
    Elem* alloc = new Elem[hi-lo];
    assert(alloc); // this is redundant; alloc throws bad_alloc
    array = (uintptr_t)(alloc) - (uintptr_t)(lo * sizeof(Elem));
    // Convert offset to unsigned to avoid overflow UB.
  }


  //////////////////////////////////////////////////////////////////
  // UNCHECKED access utilities (these method names start with "_").

  uintptr_t _get_array(){return array;}
  // Provide direct access to the base pointer (be careful!)

  Elem& _at(ptrdiff_t i)
  {return *(Elem*)(array + (uintptr_t)(i * sizeof(Elem)));}
  // Return reference to element (no bounds checking)
  // On GCC 5.4.0 with -O3, this compiles to an 'lea' instruction

  Elem* _get_alloc(){return &_at(lo);}
  // Return zero-based array that was allocated

  ~Array() {delete[](_get_alloc());}


  //////////////////////////////
  // SAFE access utilities

  Elem& at(ptrdiff_t i)
  {
    if (i < lo || i >= hi)
      {
        ostringstream msg;
        msg << "Array.at(): " << i << " is not in range ["
            << lo << ", " << hi << "]";
        throw out_of_range(msg.str());
      }
    return _at(i);
  }

  int get_lo() const {return lo;}
  int get_hi() const {return hi;}
  int size()   const {return hi - lo;}

  Elem& operator[](ptrdiff_t i){return at(i);}
  // std::vector is wrong; operator[] is the typical use and should be safe.
  // It's good practice to fix mistakes as we go along.

};


// Test
int main() 
{  
  const int LO = 1000000000;
  const int HI = LO + 10;
  Array<int> array(LO, HI);
  for (int i=LO; i<HI; i++)
    array[i] = i;
  for (int i=LO; i<HI; i++)
    cout << array[i] << "\n";
}

Обратите внимание, что все еще невозможно привести неверный «указатель», вычисленный с помощью intptr_t, к типу указателя из-за GCC 4.7Массивы и указатели :

При приведении от указателя к целому и обратно, результирующий указатель должен ссылаться на тот же объект, что и исходный указатель , в противном случае поведение не определено,То есть нельзя использовать целочисленную арифметику, чтобы избежать неопределенного поведения арифметики с указателями, как это запрещено в C99 и C11. 6.5.6 / 8.

Именно поэтому поле array должно иметь типintptr_t, а не Elem*.Другими словами, поведение определяется до тех пор, пока intptr_t настроен так, чтобы указывать на исходный объект, прежде чем преобразовать обратно в Elem*.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...