我们假设我们有一个2D int
数组:
int a[3][4] = { { 1,3,2,4 }, { 2,1,5,3 }, { 0,8,2,3 } };
获取其地址并将其重新解释为指向int
的1D数组的指针是否合法且有效?基本上是:
int *p = reinterpret_cast<int *>(&a);
这样我就可以做(粗略地):
template<typename T, size_t X, size_t Y>
void sort2(T(&arr)[X][Y])
{
T *p = reinterpret_cast<T *>(&arr);
std::sort(p, p + X*Y);
}
DEMO:https://ideone.com/tlm190
据我所知,该标准保证2D阵列的对齐在内存中是连续的,虽然技术上超出范围的p + X*Y
永远不会被访问,因此也不应导致未定义的行为。
我可以在需要时将2D数组绝对视为一维数组吗?
答案 0 :(得分:4)
谢谢大家的回复和评论,但我认为正确的答案是 - 因为代码展示了技术UB,尽管可以纠正。我查看了其中一些问题[1,2] @xskxzr,并将其引导至this quote from the standard:
如果两个对象是指针可互换的,那么它们具有相同的 地址,并且可以从指针获得指向一个的指针 通过 reinterpret_cast 到另一个。 [注意:一个数组对象及其 第一个元素不是指针可互换的,即使它们有 相同的地址。 - 结束记录]
然后在reinterpret_cast
page上有以下注释和示例:
假设满足对齐要求, reinterpret_cast 可以 不要在几个有限的情况之外更改指针的值 处理指针可互换的对象:
int arr[2];
int* p5 = reinterpret_cast<int*>(&arr); // value of p5 is unchanged by reinterpret_cast and
// is "pointer to arr"
即使这是compiles without warning and runs,这在技术上也是一个UB,因为p5
在技术上仍然是指向arr
而不是arr[0]
的指针。所以基本上reinterpret_cast
使用它的方式导致了UB。考虑到上述情况,如果我要将int *
直接创建到第一个int
(根据@codekaizer的答案,这是可以的),那么这应该是有效的,对吧?:
template<typename T, size_t X, size_t Y>
void sort2(T(&arr)[X][Y])
{
T *p = &arr[0][0]; // or T *p = arr[0];
std::sort(p, p + X * Y);
}
但它也可能是UB,因为指针p
指向具有T
元素的T
的第一个数组的第一个Y
。因此,p + X*Y
将指出第一个T
数组的范围,因此UB(再次感谢@xskxzr为link和评论)。
如果表达式P指向具有n的数组对象x的元素x [i] 元素,表达式P + J和J + P(其中J的值为j) 如果0≤i+j≤n,则指向(可能是假设的)元素x [i + j]; 否则,行为未定义。
所以这是我放弃之前的最后一次尝试:
template<typename T, size_t X, size_t Y>
void sort2(T(&arr)[X][Y])
{
T(&a)[X * Y] = reinterpret_cast<T(&)[X * Y]>(arr);
std::sort(a, a + X * Y);
}
此处T arr[X][Y]
首先转换为T a[X*Y]
,再次reinterpret_cast
,我认为现在有效。重新解释的数组a
愉快地衰减到指向数组a[X*Y]
的第一个元素的指针(a + X * Y
也在该范围内)并转换为std::sort
中的迭代器。
TL; DR版
由于reinterpret_cast
的使用不当,OP中的行为未定义。将2D数组转换为1D数组的正确方法是:
//-- T arr2d[X][Y]
T(&arr1d)[X*Y] = reinterpret_cast<T(&)[X*Y]>(arr2d);
T1类型的左值表达式可以转换为引用 另一种类型T2。结果是lvalue或xvalue指的是 与原始左值相同的对象,但具有不同的类型。没有 临时创建,没有复制,没有构造函数或转换 函数被调用。只能访问生成的引用 如果允许类型别名规则
,则安全
每当尝试读取或修改存储的值时 DynamicType类型的对象,通过类型为AliasedType的glvalue, 除非满足以下条件之一,否则行为是未定义的:
- AliasedType和DynamicType相似。
非正式地,如果忽略顶级,则两种类型相似 CV-资格
- 它们都是相同大小的数组或两个未知范围的数组,并且数组元素类型相似。
在声明
T
D
中D
的格式为
D1 [ constant-expression opt ] attribute-specifier-seq opt
并且声明
T
D1
中的标识符类型为 “ derived-declarator-type-listT
”,然后D
标识符的类型是数组类型;如果D
的标识符类型包含auto 类型说明符,则程序格式错误。T
称为数组元素类型;
答案 1 :(得分:3)
来自http://www.cplusplus.com/doc/tutorial/arrays/
int jimmy [3][5]; // is equivalent to
int jimmy [15]; // (3 * 5 = 15)
创建数组(任何维度)时,数组内存是size = sizeof(type) * dim0 * dim1 * ....;
所以对于你的问题,是的,你可以安全地将数组重铸成一维数组。
答案 2 :(得分:2)
是。这是合法且有效的。
根据dcl.array:
如果E是秩i×j×⋯×k的n维数组,则出现E 受数组到指针转换的表达式是 转换为指向具有等级j×⋯×k的(n-1)维数组的指针。 如果是
*
运算符,则显式或隐式地作为结果 下标,应用于此指针,结果是指向的 (n-1) - 维数组,它本身立即转换为 指针。
答案 3 :(得分:0)
2D数组不能被视为1D数组
正如@KillzoneKid所指出的那样,即使数组的地址共享相同的地址值,它的指针也不能与第一个元素的指针互换。
constexpr
提供了一种评估UB的便捷方法。评估constexpr
时,要求编译器检测UB。因此,可以进行简单的测试。在编译时对其进行评估时,该函数将检测到超出数组范围的访问值。
// sum "n" sequential ints
constexpr int sum(const int* pi, int n) {
int sum = 0;
while (n-- > 0)
sum += *pi++;
return sum;
};
在运行时使用指向1D或2D数组的第一个元素的指针调用此函数时,它将对元素求和,但如果“ n”超出数组的边界,则为UB。对于2D阵列,这将是最小尺寸的范围。不是整个数组的大小。
检查指向int的指针的不同实例后,一旦尝试访问数组维之外的值,就会看到UB发生。
int main()
{
constexpr int i0{1};
constexpr int i1[2]{ 1,2 };
constexpr int i2[2][2] = { { 1,2}, {3,4} };
constexpr int sum0 = sum(&i0, 1); // fails for n>1
constexpr int sum1 = sum(&i1[0], 2); // fails for n>2
constexpr int sum2 = sum(&i2[0][0], 2); // fails for n>2
const int sum3 = sum(&i2[0][0], 4); // runtime calc, UB. fails if constexpr
return sum0 + sum1 + sum2 + sum3; // 17
}
对于超出现有数据(例如sum0和sum1)的访问,UB被清除。但是sum2指向n = [0:3)的现有数据,但constexpr评估显示,n = 4时它是UB。
我对此感到有些惊讶。在执行诸如将矩阵的所有系数缩放为固定数量之类的事情时,我使用过的每个编译器都按预期工作。但是我可以看到基于优化假设的基本原理,即矩阵的各个部分不会因不在同一数组序列中的另一部分的函数调用结果而改变。