c++ - 用于非基于零的数组指针分配的 C++ gcc 扩展?
问题描述
我正在寻找支持 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)指向(可能是假设的)元素 x[i + j] 如果 0 ≤ i + j ≤ n;否则,行为是 undefined。同样,如果 0 ≤ i - j ≤ n,表达式 P - J 指向(可能是假设的)元素 x[i - j];否则,行为未定义。
换句话说,上面代码中标记为 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
寄存器,再加上 1 个用于i
? (类似地,如果冷取数组引用,我们应该能够直接获取非从零开始的指针,而不需要仅仅为了在寄存器中建立引用而进行计算)
解决方案
假设您在 linux x86-64 上使用 gcc,它支持可以保存任何指针值(有效或无效)的intptr_t
and类型,并且还支持整数运算。更适合此应用程序,因为它支持mod 2^64 语义,同时具有 UB 案例。uintptr_t
uintptr_t
intptr_t
正如评论中所建议的,我们可以使用它来构建一个重载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 Arrays and Pointers,仍然无法将计算出的无效“指针”转换为指针类型:
当从指针转换为整数并再次返回时,生成的指针必须引用与原始指针相同的对象,否则行为未定义。也就是说,不能使用整数算术来避免指针算术的未定义行为,如 C99 和 C11 6.5.6/8 中所禁止的。
这就是array
字段必须是 typeintptr_t
而不是的原因Elem*
。换句话说,只要intptr_t
在转换回 之前将 调整为指向原始对象,就定义了行为Elem*
。
推荐阅读
- jhipster - jHipster mongock 迁移
- javascript - 数组的复杂问题
- ios - NWPathMonitor 的 pathUpdateHandler 块在应用程序后台到前台转换时被调用
- three.js - Three.js 中着色器中的多个缓冲区。使用 setRenderTarget 而不是 renderTarget 的 WebGL
- javascript - SlickJS 在加载时垂直对齐幻灯片,但在与箭头交互时修复布局
- angular - Angular 本地化 messages.en.xlf 源和目标相同的内容
- c - C语言中使用_Generic混合函数调用并返回常量的重载函数
- javascript - node_modules 中的代码未反映在浏览器中
- c# - Blazor 未在子文件夹中发现新页面
- python - 如何在我的 *.wav 文件中命名频道/曲目?