[英]Treating 2D array as 1D array
假设我们有一个 2D int
数组:
int a[3][4] = { { 1,3,2,4 }, { 2,1,5,3 }, { 0,8,2,3 } };
取其地址并将其重新解释为指向int
的一维数组的指针是否合法有效? 基本上:
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);
}
演示: https : //ideone.com/tlm190
据我所知,该标准保证二维数组的对齐在内存中是连续的,虽然p + X*Y
技术上讲是超出范围的,但永远不会被访问,因此也不应该导致未定义的行为。
我可以在需要时绝对将二维数组视为一维数组吗?
感谢大家的回复和评论,但我认为正确的答案是 - 就目前而言,代码展示了技术 UB,但可以更正。 我浏览了其中的一些问题 [ 1 , 2 ] @xskxzr 链接,这让我从标准中找到了这句话:
如果两个对象是指针可互转换的,则它们具有相同的地址,并且可以通过reinterpret_cast从指向另一个的指针获取指向一个的指针。 [注意:一个数组对象和它的第一个元素是指针不可转换的,即使它们有相同的地址。 —尾注]
然后在reinterpret_cast
页面上有以下注释和示例:
假设满足对齐要求, reinterpret_cast不会在处理指针可互转换对象的一些有限情况之外更改指针的值:
int arr[2];
int* p5 = reinterpret_cast<int*>(&arr); // value of p5 is unchanged by reinterpret_cast and
// is "pointer to arr"
即使这在没有警告的情况下编译并运行,这在技术上是一个 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
指向具有Y
元素的T
s 的第一个数组的第一个T
因此p + X*Y
将指向第一个T
数组的范围,因此是 UB (再次感谢@xskxzr 提供链接和评论)。
如果表达式 P 指向具有 n 个元素的数组对象 x 的元素 x[i],则表达式 P + J 和 J + P(其中 J 的值为 j)指向(可能是假设的)元素 x[i+ j] 如果 0≤i+j≤n; 否则,行为未定义。
所以这是我放弃之前的最后一次尝试:
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 中的行为未定义。 将二维数组转换为一维数组的正确方法是:
//-- T arr2d[X][Y]
T(&arr1d)[X*Y] = reinterpret_cast<T(&)[X*Y]>(arr2d);
T1 类型的左值表达式可以转换为对另一个类型 T2 的引用。 结果是一个左值或 xvalue 引用与原始左值相同的对象,但具有不同的类型。 没有临时创建,没有复制,没有构造函数或转换函数被调用。 只有在类型别名规则允许的情况下才能安全地访问结果引用
别名规则:
每当尝试通过 AliasedType 类型的泛左值读取或修改 DynamicType 类型的对象的存储值时,除非满足以下任一条件,否则行为未定义:
- AliasedType 和 DynamicType 类似。
非正式地,两种类型是相似的 if,忽略顶级 cv-qualification
- 它们都是相同大小的数组或都是未知边界的数组,数组元素类型相似。
在声明
T
D
,其中D
的形式为
D1 [ constant-expression opt ] attribute-specifier-seq opt
并且声明
T
D1
中的标识符类型为“ derived-declarator-type-listT
”,则D
的标识符类型为数组类型; 如果D
的标识符类型包含 auto类型说明符,则程序格式错误。T
称为数组元素类型;
来自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 * ....;
固定的内存块size = sizeof(type) * dim0 * dim1 * ....;
所以对于你的问题,是的,你可以安全地将数组重铸为一维数组。
是的。 它是合法有效的。
根据dcl.array :
如果 E 是秩为 i×j×⋯×k 的 n 维数组,则出现在需要进行数组到指针转换的表达式中的 E 将转换为指向 (n−1) 维数组的指针秩为 j×⋯×k。 如果
*
运算符(作为下标的结果显式或隐式)应用于此指针,则结果是指向的 (n-1) 维数组,该数组本身立即转换为指针。
二维数组不能被视为一维数组
正如@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;
};
当在运行时使用指向一维或二维数组的第一个元素的指针调用它时,它将对元素求和,但如果“n”超出数组的边界,则为 UB。 对于二维数组,这将是最低维度的范围。 不是整个数组的大小。
检查指向 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。
得知这一点,我有些惊讶。 我使用过的每个编译器在执行诸如按固定量缩放矩阵的所有系数之类的操作时都按预期工作。 但是我可以看到基于优化假设的基本原理,即矩阵的部分不会因对不在同一数组序列中的另一部分的函数调用的结果而改变。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.