C语言 指针指向数组vs指针

dkqlctbz  于 2023-10-16  发布在  其他
关注(0)|答案(5)|浏览(125)

我检查了很多问题,问在stackoverflow,但没有得到确切的答案,我的问题。顺便说一句,我的问题是关于C。

int a[10] = {0,1,2,3,4,5,6,7,8,9};
int (*p)[10]; // ı know it is pointer to int array that consist of 10 elements.
int *d;

d = a;
p = &a; // it is array type

printf("*p = %p , p = %d", (void *)*p, (void*)p); // it prints same thing, start address of "a" array 
printf("*d = %p, d = %p",*d,d); // it prints a[0] and start address of a respectively.

我已经知道“p”是一个类型化的数组,但实际上它是一个数组指针(它在内存中占用8个字节,就像大多数系统中的地址数据“d”一样)。当不引用“p”时,不期望“a[0]"。编译器以某种方式处理它并返回“a”数组的地址数据。为什么?这是标准还是历史问题?因为编译器没有找到编译器这样做的任何原因。它通常可以被解引用为“d”。

okxuctiv

okxuctiv1#

int a[10] = {0,1,2,3,4,5,6,7,8,9};
int (*p)[10]; // ı know it is pointer to int array that consist of 10 elements.
int *d;

d = a;

这里,d是指向数组元素的指针,即a的第一个元素。赋值是正确的,因为a本身在表达式中的意思就是,* 第一个元素的地址。

p = &a; // it is array type

这里,你将a数组的地址赋给p(这是一个指向10个int元素的数组的指针,所以赋值是正确的,两个指针都指向10个int s的数组),所以p * 几乎 * 是a的别名。但是p仍然是一个指向数组的指针(所以它存储一个地址),而a不是。a是一个有10个整数的数组,而不是一个指针。

printf("*p = %p , p = %d", (void *)*p, (void*)p); // it prints same thing, start address of "a" array

这里,p是一个指向数组的指针,所以*p本身就是一个数组,并且,当一个数组出现在表达式中时,它被解释为第一个元素的地址,它实际上等价于&p[0],它是一个指向int的指针,指向数组的第一个元素。对于普通的p,将指向数组的指针转换为void *指针,并将其传递给printf()例程,获得相同的值(数组从它的第一个元素开始,所以两个值应该相等,但在这种情况下你打印的是数组的地址,而不是第一个元素的地址)我知道这很奇怪,但是你永远不能在表达式中使用数组,所以当一个表达式作为一个整体产生一个数组时,编译器会生成代码来提取第一个元素的地址。

printf("*d = %d, d = %p",*d,d); // it prints a[0] and start address of a respectively.

在本例中,d是指向int的指针,它已被初始化为数组a的开头。所以*dd指向的实际元素,你得到 * 数组单元值 *,而不是第一个元素的地址(这应该是0,但尝试在初始化中更改数组单元以确认)。您将其转换为void *(这将导致Undefined Behaviour,因此实际值可以为0或不同,不要介意)并打印它。当然,d是指针值,如果你将其转换为void *并将其传递给printf(),你将再次获得a的虚拟地址。
你应该考虑几件事:

***数组和指针不是一回事。**要寻址(通过pp指向的数组的整数值,需要两次解引用(第一次是通过指针p访问数组时,第二次是在执行所需的指针算术以获得数组元素的偏移量之后。但是d只是一个指向int的普通指针,你不能两次反引用,编译器也不会。

  • 隐式解引用发生在您命名数组时(直接或间接,作为某些子表达式计算的结果),并按此方式使用。你没有显示代码的结果,但是,(我也没有这样做,我为此道歉)恕我直言,你在这个过程中的某个时候缺乏(在你的脑海中)一些引用(可能是我上面所说的那个).数组可以被访问,但不能用作变量,不能按值传递它们,不能对它们进行排序(除非通过一些知道数组内部结构的例程,并将指向第一个元素的指针传递给它,该指针仅表示其名称)不能将数组作为一个整体进行赋值,因为不能在C中使用数组。你可以通过分析你求值的子表达式的类型来解决求值问题,如下所示:
  • 当你计算*p时,你得到一个数组(因为p被定义为指向一个10个元素的数组的指针),所以编译器立即构建一个指向该数组第一个元素的指针(编译器在这里做了两次解引用,一次),使它在语义上确实与&a[0]相同。您可以通过两个步骤进行转换...知道p指向a。首先将*p更改为a,然后将a更改为&a[0]。当你计算*d时,你是在计算一个指向int的指针(它实际上指向a的第一个元素并解引用,所以你实际上获得了数组单元格的内容,它是一个普通的整数。在这种情况下,没有这样的两个取消引用,而只有您指定的一个。

如果去掉所有类型转换,您可能只会在表达式*d中看到一个警告,因为它是一个整数,所以不匹配指针。这是一个GCC扩展,可以帮助您识别错误,因为传递的值实际上不是指针,而格式字符串中匹配的格式说明符实际上需要指针。

sg24os4d

sg24os4d2#

因为p的类型是int (*)[10],指向大小为10的数组的指针,解引用的p的类型为int[10],即数组大小10。该数组类型作为参数传递给函数,导致数组衰减为指向其第一个成员的指针,结果类型为int *
所以这条线告诉你:

printf("*p = %p , p = %d", (void *)*p, (void*)p);

数组本身的地址与其第一个成员的地址相同。

mpbci0fu

mpbci0fu3#

以下所有人都有相同的地址:

  • 数组a
  • 数组中的第一项&a[0]
  • 指向数组中第一项的指针的值。d
  • 指向数组的指针的值。p

当解引用p时,得到数组a。其行为类似于任何数组:每当在表达式中使用时,它都会“衰减”为指向其第一个元素的指针。
当不引用“p”时,不期望“a[0]”
这是错误的。为了做到这一点,你必须做两次解引用,**p

ecfsfe2w

ecfsfe2w4#

关于“指针与指向数组的指针”的另一件事。
在你的例子中,“p”指向整个数组。如果执行指针递增/递减-它将指向数组后的第一个地址,即每次当你执行相同的操作时,它将以int10 bytes = 40 bytes的方式移动,与它指向的当前地址相比(在你的例子中)。
另一方面,你有一个“d”,它是一个常规指针-它的增量/减量与它所属的数据类型大小一样多。
特别是“d”是多余的,因为数组本身就是一个指针。如果你删除了“[]”括号,你可以直接对数组名应用“
”解引用操作符,以提取存储到它所指向的地址的值。你可以用指针算法来代替“[]”括号来遍历数组。

gxwragnw

gxwragnw5#

一些尚未涉及的其他问题:

说明符不匹配

不要打印带有"%p"和risk undefined behavior(UB)的int
启用所有警告并使用匹配的说明符。

int *d;
...
// printf("*d = %p, d = %p",*d,d);
printf("*d = %d, d = %p",*d,d);

高级:等同,但不一定是相同的位模式

  • 某种方式编译器处理它并返回“a”数组的地址数据。*
printf("*p = %p , p = %d", (void *)*p, (void*)p);

*pp都是指针,但类型不同。
p是数组a的地址。
*p是数组元素a[0]的地址,int的地址。
即使在转换为void *之后,结果也可能具有不同的位模式。
(void *)*p == (void*)p仍然是正确的。

相关问题