数组指针衰减和将多维数组传递给函数

17

我知道数组会衰变为指针,这意味着如果声明

char things[8];

然后稍后在其他地方使用thingsthings是指向数组中第一个元素的指针。

此外,据我所知,如果声明了

char moreThings[8][8];

那么moreThings实际上不是指向char类型的指针,而是"指向char指针的数组"类型,因为衰减只发生一次。

当将moreThings传递给函数(比如原型为void doThings(char thingsGoHere[8][8])的函数),堆栈上实际发生了什么?

如果moreThings不是指针类型,那么这真的还是按引用传递吗?我想我一直认为moreThings仍表示多维数组的基地址。如果doThings接受输入thingsGoHere并将其传递给其他函数呢?

规则是否大致是,除非将数组输入指定为const,否则该数组将始终可修改?

我知道类型检查只发生在编译时,但我仍然困惑于什么技术上算是按引用传递(即仅当传递指针类型的参数时才会发生,还是指向指针的数组也算按引用传递?)

对于这个问题有点散乱,抱歉,但由于难以理解,很难表达一个精确的问题。

3个回答

31
你的理解稍有偏差:moreThings 也会退化为指向第一个元素的指针,但因为它是一个字符数组的数组,第一个元素是一个“包含8个字符的数组”。因此退化后的指针类型如下:
char (*p)[8] = moreThings;
指针的当然与第一个元素的值&moreThings[0][0]相同,也与&a相同,但是每种情况下类型都不同。

如果char a[N][3],这里有一个例子:

+===========================+===========================+====
|+--------+--------+-------+|+--------+--------+-------+|
|| a[0,0] | a[0,1] | a[0,2]||| a[1,0] | a[1,1] | a[1,2]|| ...
|+--------+--------+-------+++--------+--------+-------++ ...
|            a[0]           |            a[1]           |
+===========================+===========================+====
                                    a
^^^
||+-- &a[0,0]
|+-----&a[0]
+-------&a
  • &a: 整个字符数组数组的地址,它是一个char[N][3]

  • &a[0],与a相同:第一个元素的地址,它本身是一个char[3]

  • &a[0][0]:第一个元素的第一个元素的地址,它是一个char

这表明不同的对象可能具有相同的地址,但如果两个对象具有相同的地址且相同的类型,则它们是同一个对象。


19

"数组地址和多维数组的指针"

  • 让我们先从一维数组开始:

  • 声明char a[8];创建一个包含8个元素的数组。
    在这里,a第一个元素的地址,但不是整个数组的地址

  • char* ptr = a;是正确的表达式,因为ptr是指向char的指针,可以寻址到第一个元素。

  • 但是ptr = &a这个表达式是错误的!因为ptr不能寻址一个数组。

  • &a表示数组的地址。实际上,a&a的值相同,但语义上它们是不同的。其中一个是char的地址,另一个是包含8个char的数组的地址。

  • char (*ptr2)[8];这里ptr2是指向8个字符的数组的指针,这次ptr2=&a是有效的表达式。

  • &a的数据类型是char(*)[8]a的类型是char[8],大多数操作中会简单地转换为char*,例如char* ptr = a;

    想要更好地理解,请阅读:char *str和char str[]之间的区别以及它们在内存中的存储方式有何不同?

  • 第二种情况:

  • 声明char aa[8][8];创建一个大小为8x8的二维数组。

  • 任何二维数组也可以被视为一个一维数组,在这个一维数组中,每个数组元素都是一个一维数组。

  • aa是第一个元素(包含8个字符的数组)的地址。表达式ptr2 = aa是有效且正确的。

  • 如果我们声明如下:

  • char (*ptr3)[8][8];    
    char ptr3 = &aa;  //is a correct expression
    

    同样地,
    在你的声明char moreThings[8][8];中,moreThings包含一个指向8个元素的字符数组的第一个元素的地址。

    为了更好地理解,请阅读:Difference between char* str[] and char str[][] and how both stores in memory?


有趣的是:

  • morething是一个8个字符数组的地址。

  • *morething是指向第一个元素的地址,即&morething[0][0]

  • &morething是一个8 x 8的二维数组的地址。

    所有以上三者的地址值相同,但语义上都不同。

  • **morething是第一个元素的值,即morething[0][0]

    为了更好地理解,请阅读:Difference between &str and str, when str is declared as char str[10]?

此外,

  • void doThings(char thingsGoHere[8][8])不过是void doThings(char (*thingsGoHere)[8])的简写形式,因此接受任何第二维为8的二维数组。

关于C和C++中变量类型:(我想在答案中添加)

  • C中没有按引用传递的概念,这是C++的概念。如果在C中使用它,那就意味着作者在谈论指针变量。
  • C支持按地址传递按值传递
  • C++支持按地址传递按值传递按引用传递

    阅读:pointer variables and reference variables

最后,

  • 数组的名称是常量标识符而不是变量。

void doThings(char thingsGoHere[8][8]) 实际上等同于 void doThings(char (*thingsGoHere)[8]),因此接受任何第二维为 8 的二维数组,因此“仅接受大小为 8*8 的 char 二维数组”是不正确的。 - legends2k
1
@legends2k 你说得对,我可能需要修改一下,现在我已经加入了一些进一步的参考资料-非常感谢你纠正我! - Grijesh Chauhan
很高兴看到声望很高的人欣赏纠正,许多人只是纠正答案而忽略了评论。 - legends2k

5

Kerrek已经解释得很清楚了,

另外,我们可以通过以下例子来证明:

#include <stdio.h>

int main ()
{
 int a[10][10];

 printf (".. %p  %p\n", &a, &a+1);
 printf (".. %p  %p \n ", &a[0], &a[0]+1);
printf (".. %p   %p \n ", &a[0][0], &a[0][0] +1);
}

输出结果为:
.. 0x7fff6ae2ca5c  0x7fff6ae2cbec    = 400 bytes difference
.. 0x7fff6ae2ca5c  0x7fff6ae2ca84    = 40 bytes difference
 .. 0x7fff6ae2ca5c   0x7fff6ae2ca60  = 4 bytes difference. 

&a +1 -> 通过添加整个数组大小移动指针。即:400字节。

&a[0] + 1 -> 通过添加列的大小移动指针。即:40字节。

&a[0][0] +1 -> 通过添加元素的大小移动指针。即:4字节。

[ int类型的大小为4字节 ]

希望这有所帮助。:)


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