混淆从"array of int[]"到"pointer to int"的显式衰减?

Confusing explicit decay from "array of int[]" to "pointer to int"?

本文关键字:int to 衰减 pointer array of      更新时间:2023-10-16

我是一个新手,正在按随机顺序学习C++。

在下面的前三种情况下,我可以理解正在发生的事情,因为隐式衰减的模式是清楚的。

"X的数组"隐式衰减为"指向X的指针"。

void case1()
{
int a[] = { 1,2,3,4,5,6 };
int *b = a;// array of int ---> pointer to int
}
void case2()
{
int input[][3] = { {1,2,3},{4,5,6} };
int(*output)[3] = input;// array of int[] ---> a pointer to int[]
for (int i = 0; i < 2; i++)
for (int j = 0; j < 3; j++)
cout << output[i][j] << endl;
}
int case3()
{
int input[][3] = { {1,2,3},{4,5,6} };
int* aux[2];
aux[0] = input[0];// array of int ---> pointer to int
aux[1] = input[1];// array of int ---> pointer to int
int** output = aux;// array of int* ---> pointer to int*
for (int i = 0; i < 2; i++)
for (int j = 0; j < 3; j++)
cout << output[i][j] << endl;
}

问题

然而,我真的对以下第四种情况感到困惑。

int case4()
{
int input[][3] = { {1,2,3},{4,5,6} };
int* aux[2];
aux[0] = (int*)input;// array of int[] ---> pointer to int
aux[1] = aux[0] + 3;
int** output = aux;// array of int* ---> pointer to int*
for (int i = 0; i < 2; i++)
for (int j = 0; j < 3; j++)
cout << output[i][j] << endl;
}

如何将"int[]的数组"显式衰减为"指向int的指针"?

aux[0] = (int*)input;// array of int[] ---> pointer to int

欢迎任何简单的解释!

如何将"一个int[]数组"显式衰减为"一个指向int的指针"?

对术语过于迂腐:"显式衰退"是一种矛盾修辞法。衰减根据定义是一种隐式转换。

要回答"如何将[数组]显式[转换]为[不是第一个元素类型的指针]?">

这是因为数组可以衰减为指针,并且所有数据指针都可以显式转换(重新解释)为任何其他数据指针类型。在这种情况下,input衰减为int(*)[3],然后显式地转换为int*

尽管它的形式确实很好,但另一个问题是通过显式重新解释的指针进行间接是否定义了行为。指针重新解释的规则是复杂而微妙的——假设它保证按照你观察到的方式行事是不安全的。我会更有信心写作:

aux[0] = *input;

这里,input数组衰减为指向第一个子数组的指针,该指针被间接地获取左值,然后它衰减为指向子数组元素的指针。


通常,在使用显式转换(T)expr或函数转换T(expr)或重新解释强制转换reinterpret_cast<T>(expr)时要非常小心。除非你能引用标准规则,让它们的使用得到很好的定义,否则不要使用它们。

嗯,这听起来很有效,但合法吗。

已知的是,数组的第一个元素和数组本身在内存中共享相同的地址。

在常见的实现中,所有指针共享相同的表示,并且该表示只是其第一个字节的内存地址。简单地说,标准并不能保证这一点。

但是,成功地将数组的地址转换为其第一个元素的地址就足够了。显式强制转换将数组地址的表示形式重新解释为第一个元素的地址,由于实现的原因,它恰好起作用。

这就是未定义行为的伟大之处:它并不禁止自然行为,但也不能保证它。

因此,根据标准的字符串读取,特别是严格混叠规则,将指向数组的指针转换为指向其第一个元素的指针,并使用该指针解引用元素是UB,因为数组及其元素类型是不同的类型。只需AFAIK,它就可以在所有常见的实现中工作。

TL/DR:它适用于所有常见的实现,但如果您想编写符合标准的程序,请不要使用它。