C ++ gcc扩展用于基于非零的数组指针分配?

时间:2019-03-01 20:41:00

标签: c++ arrays gcc allocation

我正在寻找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指向具有n的数组对象x的元素x [i]   元素,表达式P + J和J + P(其中J的值为j)   如果0≤i + j≤则指向(可能是假设的)元素x [i + j]   n; 否则,行为是不确定的。同样,表达式P-   如果0≤i-j,则J指向(可能是假想的)元素x [i-j]   ≤n;否则,行为是不确定的。

换句话说,上面代码中标记为FIXME的行的行为未定义,因为它为基于0的数组x[0..n]计算出的指针不在x范围内。

--std=...上是否有一些gcc选项可以告诉它允许直接计算基于非零的数组指针?

如果不是,是否存在一种合理的可移植方式来模拟return new Type[hi-lo] - lo;语句,也许是通过转换为long然后返回? (但那我会担心引入更多的错误)

此外,可以像上面的代码那样以仅需要1个寄存器来跟踪每个数组的方式来完成此操作吗?例如,如果我有array1[i], array2[i], array3[i],则只需要3个寄存器用于数组指针array1, array2, array3,再需要一个寄存器用于i? (类似地,如果冷获取数组引用,我们应该能够直接获取基于非零的指针,而不必进行计算仅在寄存器中建立引用)

1 个答案:

答案 0 :(得分:1)

假设您在Linux x86-64上使用gcc,它支持intptr_tuintptr_t类型,它们可以保存任何指针值(有效或无效),还支持整数算术。 uintptr_t更适合此应用程序,因为它支持mod 2^64 semantics,而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";
}

请注意,由于GCC 4.7 Arrays and Pointers,仍然无法将intptr_t计算出的无效“指针”转换为指针类型:

  

当从指针转换为整数并再次返回时,结果指针必须引用与原始指针相同的对象,否则行为是不确定的。也就是说,不能使用整数算术来避免C99和C11 6.5.6 / 8禁止的指针算术的不确定行为。

这就是array字段必须为intptr_t类型而不是Elem*类型的原因。换句话说,只要将intptr_t调整为指向原始对象,然后再转换回Elem*,就可以定义行为。