指针数组和指向数组第一个元素的指针之间的区别

15

int (*arr)[5] 表示 arr 是一个指向包含5个整数的数组的指针。那么这个指针到底是什么意思呢?

如果我声明 int arr[5],并且 arr 是指向第一个元素的指针,那么它是否与上面的例子相同?

这两个例子中的 arr 是否相同?如果不是,那么什么是指向数组的指针?


【相关常见问题解答】(https://dev59.com/8m445IYBdhLWcg3wia2V) - fredoverflow
6个回答

19

理论

首先介绍一些理论(你可以跳到“答案”部分,但我建议你也阅读一下这部分):

int arr[5]

这是一个数组,"arr"不是数组的第一个元素的指针。在特定情况下(例如将它们作为左值传递给函数),它们会转化为指针:您失去了对它们调用sizeof的能力。
在正常情况下,数组是数组,指针是指针,它们是两个完全不同的东西。
当处理衰减指针和您编写的数组的指针时,它们的行为完全相同,但有一个警告:类型为T的数组可以衰减为类型为T的指针,但仅限一次(或一级深度)。新创建的衰减类型不能进一步衰减为其他任何内容。
这意味着像双重数组这样的数组:
int array1[2][2] = {{0, 1}, {2, 3}};

无法传递给

void function1(int **a);

因为它意味着两个级别的衰减,这不被允许(你会失去数组元素的布局)。而以下方法可以工作:

void function1(int a[][2]);
void function1(int a[2][2]);

在将一维数组作为左值传递给函数的情况下,它会自动转化为简单指针,此时你可以像使用其他指针一样使用它

答案

回答你的问题:

int (*arr)[5]

这是一个指向数组的指针,你可以将“5个整数的数组”视为它的类型,也就是说你不能用它来指向一个只有3个整数的数组。

int arr[5]

这是一个数组,除非您将其作为左值传递,否则它将始终像数组一样运行。

int* ptrToArr = arr;

在这种情况下,数组会衰减(除了我提到的所有例外),你会得到一个指针,可以根据需要使用它。
另外:否则将不允许类似这样的操作。
int (*arr)[5]
int* ptrToArr = arr; // NOT ALLOWED

Error cannot convert ‘int (*)[5]’ to ‘int*’ in initialization

它们都是指针,但区别在于它们的类型。


1
不幸的是,我一直被告知arr&arr[0]完全等价,除了在应用sizeof时的情况。然后我发现当按照这种方式思考时,存在许多令人讨厌的不一致和奇怪的行为。我喜欢这个答案!+1 - Eric

11

在运行时,指针无论它所指向的内容如何,都只是“一个指针”,不同之处在于语义上的区别;指向数组的指针与指向元素的指针在编译器中传达的意义不同。

当使用指向数组的指针时,您指向的是一个指定大小的数组 - 编译器将确保您只能指向该大小的数组。

即,此代码将编译:

int theArray[5];
int (*ptrToArray)[5];
ptrToArray = &theArray;    // OK

但是这会导致出现错误:

int anotherArray[10];
int (*ptrToArray)[5];
ptrToArray = &anotherArray;    // ERROR!

处理指向元素的指针时,您可以指向内存中任何匹配类型的对象。(甚至不需要在数组中;编译器不会做出任何假设或以任何方式限制您)

例如:

int theArray[5];
int* ptrToElement = &theArray[0];  // OK - Pointer-to element 0

还有...

int anotherArray[10];
int* ptrToElement = &anotherArray[0];   // Also OK!

总之,数据类型 int* 不意味着数组的任何知识,然而数据类型 int (*)[5] 暗示着一个必须包含恰好5个元素的数组。


根据您的解释,int* anotherPtr = theArray; 不应该起作用。 - Potatoswatter
@Potatoswatter 这只是 int* anotherPtr = &theArray[0]; 的简写语法;它们的作用完全相同。 - Ben Cottrell
2
这是一种隐式转换。它往往会引起混淆,因为许多人认为数组已经或总是指向其第一个元素的指针。 - Potatoswatter
@Potatoswatter 说得好,我已经将它编辑掉了,因为它对于答案来说并不是必要的。 - Ben Cottrell
所有这些关于编译器的谈论听起来都很可怕。有C++标准,而编译器只是尝试实现其中规定的规则。 - juanchopanza

8

数组指针是指向某种类型的数组的指针。该类型包括元素类型和大小。您不能将不同类型的数组分配给它:

int (*arr)[5]; 
int a[5];
arr = &a; // OK
int b[42];
arr = &b; // ERROR: b is not of type int[5].

指向数组第一个元素的指针可以指向具有正确类型元素的任何数组的开头(实际上,它可以指向数组中的任何元素):

int* arr; 
int a[5];
arr = &a[0]; // OK
int b[42];
arr = &b[0]; // OK
arr = &b[9]; // OK

请注意,在C和C++中,数组在某些情况下会衰变为指向其元素类型的指针。这就是为什么可以这样做的原因:

int* arr; 
int a[5];
arr = a; // OK, a decays to int*, points to &a[0]

这里,arr 的类型 (int*) 与 a 的类型 (int[5]) 不同,但是 a 会衰减为指向其第一个元素的 int*,使得赋值合法。


7

指向数组和指向数组第一个元素的指针是不同的。在 int (*arr)[5] 的情况下,arr 是指向 5int 内存块的指针。对 arr 进行解引用将给出整个行。在 int arr[5] 的情况下,arr 衰减为指向第一个元素的指针。对 arr 进行解引用将给出第一个元素。
在两种情况下,起始地址相同,但两个指针的类型不同。

如果我声明 int arr[5] 并且 arr 是指向第一个元素的指针,那么它与这两个例子是否相同? 如果不是,那么什么是指向数组的指针?

不同。要理解这一点,请参见函数的图表1

void f(void) {
    int matrix[4][2] = { {0,1}, {2,3}, {4,5}, {6,7} };
    char s[] = "abc";
    int i = 123;
    int *p1 = &matrix[0][0];
    int (*p2)[2] = &matrix[0];
    int (*p3)[4][2] = &matrix;
    /* code goes here */
}

enter image description here

这三个指针都可以定位到matrix[0][0]中的0,如果将这些指针转换为“字节地址”并在printf()中使用%p指示符打印出来,这三个指针很可能会产生相同的输出(在典型的现代计算机上)。但是,类型为int *的指针p1只指向一个单独的int,如黑色圆圈所示。类型为int (*)[2]的红色指针p2指向两个int,而指向整个矩阵的蓝色指针确实指向整个矩阵。 这些差异影响指针算术和一元*(间接)运算符的结果。由于p1指向单个int,所以p1 + 1向前移动一个int黑色圆圈1只有一个int那么大,*(p1 + 1)就是下一个int,其值为1。同样,sizeof *p1只是sizeof(int)(可能是4)。

然而,由于p2指向整个“int数组2”,所以p2 + 1将向前移动一个这样的数组。结果将是一个指向绕过{2,3}对的红色圆圈的指针。由于间接运算符的结果是一个对象,*(p2 + 1)就是整个数组对象,这可能会受到规则的限制。如果它确实受到规则的限制,那么该对象将变成指向其第一个元素的指针,即当前保存2int。如果它不受规则的限制 - 例如,在sizeof *(p2 + 1)中,将对象置于对象上下文中 - 它将保持整个数组对象。这意味着sizeof *(p2 + 1)(当然还有sizeof *p2)是sizeof(int[2])(可能是8)。


1以上内容摘自更多有关数组和指针的话题


你自己画的这个图吗? :D - Marco A.
@MarcoA.;不,这是来自一篇研究论文。 - haccks

2
整个数组的地址和第一个元素的地址被定义为相同的,因为在C++(和C)中,除了组成对象的填充之外,数组没有固有的填充。
然而,这些指针的类型是不同的。在执行某种类型转换之前,将int*与int(*)[5]进行比较就像苹果和橙子一样。
如果声明arr[5],那么arr不是指向第一个元素的指针。它是数组对象。你可以通过sizeof(arr)等于5 * sizeof(int)来观察到这一点。数组对象隐式地转换为指向其第一个元素的指针。
指向数组的指针不会隐式转换为任何东西,这可能是你困惑的另一个原因。

0
如果你写下 int arr[5],你会在栈上创建一个包含五个int数组。这将占用大小等于五个int大小的空间。
如果你写下 int (*arr)[5],你会在栈上创建一个指向包含五个int数组的指针。这将占用大小等于指针大小的空间。
如果以上内容不清楚,请注意指针与数组名字有分离的储存,指针可以指向任何东西,但是数组名字不能被分配给指向其他地方。
查看我的回答 here 以获取更多细节。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接