指针赋值概念思维块:Ptr = *array

Pointer assignment conceptual mental block: Ptr = *array

阅读本文后: 2D array and pointer in C - how to access elements?

我仍然对 'Ptr = *data' 不是取消引用感到困惑。

该问题的相关代码:

int data[4][3] = { {23,55,50},{45,38,55},{70,43,45},{34,46,60}};
int *Ptr;

Ptr = *data;   //  This is not a de-reference.  Ptr is not 23.  Why?

我在概念层面上不明白这一点。我看到'*'并且我认为 取消引用。

说 Ptr = *(data + 0), 没有帮助,对我来说它仍然意味着取消引用 我们最终取消引用地址 'data'.

任何人都可以阐明并帮助我理解吗?

感谢您的宝贵时间。

data是二维数组,贴到int (*)[3]指针。 *data 是三个元素 int 数组并贴花到 int * 指针。

Ptr 包含对该 int[3] 数组第一个元素的引用,即 23。

您需要取消引用此指针以获得整数 23

int main(void)
{
    int data[4][3] = { {23,55,50},{45,38,55},{70,43,45},{34,46,60}};
    int *ptr;

    ptr = *data;   //  This is not a de-reference.  Ptr is not 23.  Why?

    printf("%d\n", *ptr);

}

*data 在技术上是取消引用,但是,正如我们将看到的,它实际上不会导致对其引用的对象的访问。

data是数组的数组。在表达式中使用数组时,它会自动转换为指向其第一个元素的指针,除非它用作 sizeof 或一元 & 的操作数,或者是用于初始化的字符串文字数组。在 *data 中,此转换有效地将 data 更改为 &data[0],这是指向 data.

的第一个数组的指针

因为 &data[0] 是指向第一个数组的指针,所以 *data 就是那个数组。因此,我们取消引用 &data[0] 以获取它引用的数组。

但是,由于 *data 是一个数组,它也会自动转换为指向其第一个元素的指针。因此,不是 *data 导致对数组的访问,而是更改为 &(*data)[0]。包括第一个转换,即 &(*&data[0])[0]。然后,去掉自消*&,也就是&(data[0])[0]或者只是&data[0][0].

所以*data就是&data[0][0]data的第一个数组第一个元素的地址。

除非它是 sizeof 或一元 & 运算符的操作数,或者是用于在声明中初始化字符数组的字符串文字,否则 表达式 [= “T 的 N 元素数组”类型的 101=] 将被转换或“衰减”为“指向 T 的指针”类型的表达式,表达式的值将是地址数组中的第一个元素。

让我们画出数组及其值,显示每个元素的地址。对于此图,我们假设 4 字节 ints 并且地址是凭空组成的:

Address      Value    Expression                  
-------      +----+   ----------
 0x4000      | 23 |   data[0][0] 
             + -- +
 0x4004      | 55 |   data[0][1]
             + -- +
 0x4008      | 50 |   data[0][2]
             +----+
 0x400c      | 45 |   data[1][0]
             + -- +
 0x4010      | 38 |   data[1][1]
             + -- +
 0x4014      | 55 |   data[1][2]
             +----+
 0x4018      | 70 |   data[2][0]
             + -- +
 0x401c      | 43 |   data[2][1]
             + -- +
 0x4020      | 45 |   data[2][2]
             +----+
 0x4024      | 34 |   data[3][0]
             + -- +
 0x4028      | 46 |   data[3][1]
             + -- +
 0x402c      | 60 |   data[3][2]
             +----+

现在我们可以谈谈各种表达式的类型了。

让我们从表达式 data 开始。如声明的那样,表达式的类型为“int 的 3 元素数组的 4 元素数组”(int [4][3])。除非它是 sizeof 或一元 & 运算符的操作数,否则它将“衰减”为“指向 int 的 3 元素数组的指针”类型的表达式(int (*)[3]) 并将评估地址 0x4000.

接下来,我们看一下表达式*data。我们已经知道 data 的类型为“指向 int 的 3 元素数组的指针”(int (*)[3]),因此通过取消引用它,我们得到类型为“3 元素数组的表达式” int”(int [3])。除非此表达式是 sizeof 或一元 & 运算符的操作数,否则它将“衰减”为“指向 int” (int *) 的指针,并且还会求值到地址 0x4000.

现在让我们看一下表达式 data[0]data[1]data[2]data[3]。请记住,数组下标表达式 a[i] 完全等同于表达式 *(a + i) - 给定起始地址 a,偏移量 i 元素 (不是字节!)从该地址并取消引用结果。由于data指向int的三元素数组,data + 1将指向int的下一个三元素数组.因此,如果 data == 0x4000,则 data + 1 == 0x400cdata + 2 == 0x4018data + 3 == 0x4024。由于data[i] == *(data + i),每个data[i]类型将是int [3](就像上面的*data,正好等同于data[0] 因为 *data == *(data + 0) == data[0]).

希望每个 data[i][j] 在这一点上是显而易见的 - 每个 data[i] 都有类型 int [3],它“衰减”到 int *data[i][j] == *(data[i] + j)

其他一些注意事项:

  • &data 具有类型“指向 int 的 3 元素数组的 4 元素数组的指针”,或 int (*)[4][3],其值也是 0x4000 因为数组的地址与其第一个元素的地址相同。

  • 类似地,&data[i] 具有类型“指向 int 的 3 元素数组的指针”(int (*)[3]);如上面的表达式,数组的地址与第一个元素的地址相同,所以&data[0] == data[0] == 0x4000&data[1] == data[1] == 0x400c,等等

总结一下:

Expression        Type          "Decays" to        Value
----------        ----          -----------        ------
      data        int [4][3]    int (*)[3]         0x4000
     *data        int [3]       int *              0x4000
     &data        int (*)[4][3] n/a                0x4000
   data[i]        int [3]       int *              0x4000, 0x400c, 0x4018, 0x4024
  *data[i]        int           na/                23, 45, 70, 34
  &data[i]        int (*)[3]    n/a                0x4000, 0x400c, 0x4018, 0x4024
data[i][j]        int           n/a                23, 55, 50, ...           

我正在发布另一个示例,一个完整的 C 程序,这可能有助于了解其背后的逻辑

首先,一些细节

I'm still confused about the 'Ptr = *data' not being a de-reference.

嗯,这是一个取消引用,赋值中的 * 在 C 中是一个取消引用。BUT *data 不是 int:它是int[4][3],保存数组第一个元素的地址,它指向一个int,它是23.您的代码中丢失了一级间接寻址。

      Ptr  is int*
     *Ptr  is an address, &data[0][0] or simply *data
    **Ptr  is 23

理解这一点的关键是行

    Ptr = *data;

看看 gcc 在编译时说了什么:

toninho@DSK-2009:~/projects/dsp$ gcc -o tptr -Wall -Wextra -std=c17 tptr.c

tptr.c: In function ‘main’:
    tptr.c:19:9: warning: assignment to ‘int *’ from\
 incompatible pointer type int (*)[4][3]’ [-Wincompatible-pointer-types]
    19 |     Ptr = &data;
      |         ^

Microsoft 编译器说:

1>------ Build started: Project: sop-0328-a, Configuration: Debug Win32 ------
1>ptr.c
1>C:\Users\toninho\source\repos\sop-0328-a\sop-0328-a\ptr.c(19,20):
    warning C4047: '=': 'int *' differs in levels of indirection from 'int (*)[4][3]'
1>sop-0328-a.vcxproj -> C:\Users\toninho\source\repos\sop-0328-a\Debug\sop-0328-a.exe
1>Done building project "sop-0328-a.vcxproj".
========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========

clang 编译器说:

C:\Users\toninho\source\repos\sop-0328-a\sop-0328-a>clang -Wall ptr.c
ptr.c:19:13: warning: incompatible pointer types assigning
 to 'int *' from 'int (*)[4][3]'
      [-Wincompatible-pointer-types]
    Ptr = &data;
            ^ ~~~~~
1 warning generated.

而且是一样的。 您应该始终在所有编译器上启用所有警告

I don't understand this on a conceptual level. I the see ' * ' and I think de-reference.

Saying Ptr = *(data + 0), doesn't help, to me it still means de-reference and we end up de-referencing the address 'data'.

Can anyone shed some light and help me understand?

正如我所说,这是取消引用。问题是您将 int*[4][3] 传递给了 int*,没有启用编译器警告,并且 :) 没有意识到这一点。专业人士一直这样做,有时也会感到惊讶。 你应该写 Ptr = (int*) *data;

相信下面的程序能表现得更好。添加到 0 肯定没有区别。您需要添加另一个 *

请看下面的代码,还有不明白的再问。

请注意,代码末尾的行

    pointer = (int*) &many;
    printf("Using a cast 'pointer' now points to '(int*) many' \
and its value is %d\n", *pointer);

使用转换并打印

Using a cast 'pointer' now points to '(int*) many' and its value is 23

现在编译器很满意,输出是预期的 23

例子

程序的输出是

'one' is an int at address      00EFFDB4
'many'is int[4][3] at address   00EFFD7C
'pointer' is int* at address    00EFFDC0

'pointer' now points to 'one' and its value is  00EFFDB4
'pointer' now points to 'many' and its value is 00EFFD7C

'many' is a C array, a pointer pointing to the address  00EFFD7C ( &many[0][0] )
'many' content, an address, is  00EFFD7C ( *many )

Starting at this location --- 00EFFD7C ( *many ) --- we have the values of the array
First value is 23 ( **many ),
Second value is 55 ( *(1 + *many)... )
Usng a cast 'pointer' now points to '(int*) many' and its value is 23

您可以在调试器屏幕上看到地址,以及变量的实际类型(程序在第 23 行停止):

密码

#include <stdio.h>

int main(void)
{
    int*    pointer = NULL;
    int     one = 1;
    int     many[4][3] =
    {
        {23,55,50},
        {45,38,55},
        {70,43,45},
        {34,46,60}
    };
    printf("'one' is an int at address\t%p\n", &one);
    printf("'many'is int[4][3] at address\t%p\n", &many);
    printf("'pointer' is int* at address\t%p\n\n", &pointer);
    pointer = &one;
    printf("'pointer' now points to 'one' and its value is\t%p\n", pointer);
    pointer = &many;
    printf("'pointer' now points to 'many' and its value is\t%p\n", pointer);
    printf("\n'many' is a C array, a pointer pointing to the address\t%p ( &many[0][0] )\n", &many[0][0]);
    printf("'many' content, an address, is\t%p ( *many )\n", *many);
    printf("\nStarting at this location --- %p ( *many ) --- we have the values of the array\n\
First value is %d ( **many ),\n\
Second value is %d ( *(1 + *many)... )\n", *many, **many, *(1 + *many) );

    pointer = (int*) &many;
    printf("Using a cast 'pointer' now points to '(int*) many' \
and its value is %d\n", *pointer);

    return 0;

}