“int (*ptr)[4]”实际上是什么意思?与“*ptr”有什么不同?

4
int (*p)[4] , *ptr;
int a[4] = {10,20,30,40};
printf("%p\n%p\n%p",&a,a,&a[0]);
p = &a ;
//p=a;        gives error

//ptr = &a;   gives error
 ptr = a;

输出:

0x7ffd69f14710
0x7ffd69f14710
0x7ffd69f14710

我试图理解a&a&a[0]返回的内容,它是起始变量的内存地址。那么,为什么在某些赋值语句中会出现错误?

我的意思是,如果p = &a = 0x7ff...可以工作,为什么p = a = 0x7ff..不行呢?

如果可能的话,有人能否通过一个块状图来让我理解这个p和ptr实际上指向哪里。或者它们只是指向同一个地方。但它们肯定是不同的东西。


这些都指向同一个地址(数组的开头):&a,a,&a[0]。而且,**'p'** 是一个指向四个整数数组的指针。因此,如果你打印出 p+1 的地址 (printf("p = %p\np++ = %p\n", p, p+1);),你会发现该地址比 'p' 的地址大 **4 * sizeof(int)**。 - mar
7个回答

5
假设指针是激光笔,有不同的颜色(红色用于int类型指针,绿色用于数组指针...),变量是你可以使用正确的激光笔指向的东西,即你不能使用绿色激光笔指向char类型变量。
好了,现在你有一个包含4个整数的数组int a[4]。使用绿色指针将其指向:int (*green)[4] = &a;。你还有一个整数 (a[0]),可以使用红色指针指向它:int *red = &a[0]; /* 在大多数情况下,单独使用 'a' 会被转换为 "第一个元素的地址":&a[0] 和 a 是一样的 */
现在向你色盲的朋友询问指针指向哪里 :) 对于你的朋友来说,它们相等并且指向相同的“位置”...但你欺骗了你的朋友!编译器是色盲的,并且不喜欢被欺骗

谢谢!这意味着在概念上,执行p = a并没有错,但编译器就像一个喜欢遵守规则的严格人士,所以它认为如果p是绿色的,它应该只指向绿色的东西,也就是说它在这里指向数组,因此&a在语法上看起来是正确的(即使&a[0]是正确的,它也将其视为单个元素,因此不允许此赋值)。我是对的吗? - SpawN
是的... p 是一个指向包含4个整数的数组的指针,a 是(转换为)指向 int 的指针。"不要混合颜色" - pmg
编译器并不是色盲的,你想说的是;-) - Peter - Reinstate Monica
这要看情况,@Peter。通常它们并不区分颜色;比如在 printf() 中(或者处理 void* 时),它们是无差别的。 - pmg
2
在这种特殊情况下,这句话没有意义:如果他们是色盲的(“地址就是地址,它们都一样”),他们甚至不会注意到自己被欺骗了,对吧? - Peter - Reinstate Monica

3
首先,我们来看一下声明本身:
- `int * ptr` - `ptr` 是 `int *` 类型,即指向 `int` 的指针。 - `int (*ptr)[4]` - `ptr` 是 `int(*)[4]` 类型,即指向包含四个 `int` 元素的数组的指针。
这两种类型是不同的。
在 C 语言中,数组类型的表达式可以衰减(decay)成指向数组第一个元素的指针。
引用自 C18 标准,ISO/IEC 9899:2018: > “除 sizeof 操作符、一元 & 操作符或者是用来初始化数组的字符串字面量以外,在任何情况下,类型为‘数组类型’的表达式都会被转换成‘指向类型的指针’的表达式,该指针指向数组对象的第一个元素,并且不是左值。如果数组对象具有寄存器存储类型,那么行为未定义。”
但由于在 `a` 处使用了 `&` 运算符,因此 `a` 并没有衰减成指向其第一个元素的指针。相反,`&` 运算符被应用于整个数组,并产生一个指向整个数组的指针(类型为 `int (*)[4]`)。
这是一种语法上的类型差异/不匹配,即 `int *` vs. `int (*)[4]`,虽然它们都指向内存中的同一地址。
编译器有义务对任何类型不匹配抛出诊断,因为它是语法违规。
当然,这两者具有相同的地址,但赋值时的类型不兼容造成了差异。

0

p 是指向类型为 int[4] 的值的指针,即指向每个包含 4 个整数的数组的指针。请注意,sizeof(*p)sizeof(int) 的 4 倍。

现在,

  • p = a 失败了,因为当赋值时,a 会衰减为指向 int 的指针,而 p 指向不同的类型。了解更多关于衰减的知识:什么是数组到指针的衰减?
  • ptr = &a 失败了,因为 ptr 实际上是一个指向 int 的指针;它与 p 的类型不同。在同一行上声明多个变量通常会导致混淆,因为并非所有语法都适用于您声明的所有内容;最好将这些定义拆分成单独的行。

好的,所以它们的大小不同。但是,为什么有些赋值会出错,当所有赋值都只是起始变量的内存地址时? - SpawN

0
我试图理解 a&a&a[0] 的含义。
在 C 中,数组会衰变为指针。所有这些指针都引用同一个内存位置(数组的第一个元素)。唯一的区别是类型。 a&a[0] 具有数组元素的类型(在本例中为 int)。 &a 是指向元素类型的数组的指针类型(在本例中为 4 个整数的数组)。

0

这是一个关于不同类型的问题,类型是编译器中存在但不在编译后的二进制文件中存在的概念。这就是为什么即使两个指针类型实际上指向相同的地址,你仍然会得到编译器错误。

你可以将int (*p)[4]=&arr;看作是整个数组的指针,而int* ptr=arr;则是数组中第一个元素的指针

通常情况下,在表达式中使用数组名时,它会“衰变”为指向第一个元素的指针。这就是当我们写int* ptr=arr;时发生的情况——它与写int* ptr = &arr[0];完全等价。

正式地说,“数组衰减”规则在C17 6.3.2.1/3中定义:

除非它是sizeof运算符的操作数、一元&运算符的操作数或用于初始化数组的字符串字面量,否则具有“类型数组”的表达式将被转换为具有指向数组对象初始元素的类型“指向类型”的表达式,并且不是左值。
正如我们所看到的,&运算符是规则的一个特殊例外。这意味着在&arr的情况下,arr部分不会衰减。因此,我们期望得到一个指向数组类型而不仅仅是第一个元素的指针。这就是int (*p)[4]的作用所在。
但当然,“整个数组的指针”同时也将指向第一个项的地址,因为那是数组开始的地址。如果我们使用printf("%p\n", p),无论我们传递数组指针还是指向第一个元素的指针,我们都将得到完全相同的地址。

数组指针的作用是保持语言类型系统的一致性。当我们开始使用多维数组时,有时也会实际遇到它们。例如,如果我们定义一个int arr[2][3]数组,它实际上是一个由2个项目组成的数组,其中每个项目都是一个int[3]数组。那么当我们为这个二维数组键入arr时会发生什么?像往常一样,数组会衰减为指向第一个项目的指针。而第一个项目是一个数组,因此为了保持数组衰减规则的一致性,它必须给出指向这样一个由3个整数组成的数组的指针。这样一个指针的类型是int(*)[3]


0

这些讨论已经澄清了两个问题,我想总结一下:

1. int *ptr 和 int *ptr[4] 有什么区别?

答案:这两个指针变量的大小相同,因为它们都只保存地址。只是一个概念上的区别,ptr保存整数的地址。当然,您可以使用它来指向数组的起始位置。但是编译器会认为:它可以保存任何整数。当您在代码中尝试执行“ptr ++”时,它将仅将内存地址向前移动1个单位(根据该系统为整数保留的字节数)。但是,int *ptr [4]表示ptr是一个指向整个数组的指针,只存储了起始位置。当然,这两种情况下的ptr都存储相同的地址。但是,在这种情况下尝试执行“ptr ++”时,它将向前移动4个单位,因为编译器将其解释为指向数组的指针,而不是整数的指针。

2. 为什么 ptr=&a 可以工作,而 ptr =&a [0] 或 ptr=a 却不能工作,即使这些值都相同?

答案:ptr=a和ptr=&a都是概念上正确的。但是,编译器遵循严格的规则。如果您想说ptr包含整数的地址,那么它应该被分配为ptr=a或ptr=&a [0](表示分配的空间是整数)。而如果ptr被声明为数组的地址,则编译器将ptr = &a [0]或ptr = a解释为此ptr获取整数的地址,这是不正确的,因为在这种情况下,ptr表示数组的地址。它不应该在这种情况下保存整数的地址。因此,p=&a在语法上非常正确,这是它接受的唯一选项。

:)


由于运算符优先级规则,您需要括号 (*ptr)[4] - Peter - Reinstate Monica
同意。但这里的问题是关于*ptr所持有的内容。在一种情况下,它持有地址(int),而在另一种情况下,它持有地址范围(数组),其中ptr实际上持有这些地址范围的一种标识符,并且编译器根据这些概念接受代码。 - SpawN
这只是一个C语法上的注释。(但是语法很重要,因为它定义了我们所说的内容。如果你说 int *ptr[4],你定义了一个可以索引的实体ptr,结果是一个指向int的指针;换句话说,你谈论的是指针数组,而不是数组指针。数组指针需要先解除引用,然后再进行索引。这种早期的解除引用是由方括号强制执行的。)顺便说一下,所有指针都只“持有”一个数字,即地址;没有类型信息存储在其中。所有类型信息仅在编译器翻译期间存在于编译器的“思维”中。 - Peter - Reinstate Monica
想一想,在 C 中,这对于所有类型都是正确的。在像 Java 和 C# 这样的语言中,每个对象都携带有关其自身的信息,可以通过反射在运行时进行检查。在 C 中,没有像 Invalidcastexception 这样的运行时机制。数组也不存储其长度;只有在编译时,编译器才强制执行某些类型检查。 - Peter - Reinstate Monica

0

以下是基本规则:

对于任何类型T,您可以拥有以下任意一种:

T *p;        // p is a pointer to T
T *a[N];     // a is an array of pointer to T
T (*a)[N];   // a is a pointer to an array of T
T *f();      // f is a function returning pointer to T
T (*f)();    // f is a pointer to a function returning T

后缀运算符“[]”和“()”的优先级高于一元运算符“*”,因此像“*p[i]”这样的表达式被解析为“*(p[i])”。如果您想要索引到“p”所指向的内容,则需要显式地将“*”运算符与“p”分组,或者写成“(*p)[i]”。这个优先级规则适用于表达式和声明。
除非它是“sizeof”、“_Alignof”或一元运算符“&”的操作数,或者是用于在声明中初始化字符数组的字符串字面量,否则类型为“N元素数组T”的表达式将被转换(“衰减”)为类型为“指向T的指针”的表达式,并且表达式的值将是数组的第一个元素的地址。因此,给定声明:
int a[4] = {10, 20, 30, 40};

以下所有内容都是正确的:

Expression        Type            Decays to             Equivalent value
----------        ----            ---------             ----------------
         a        int [4]         int *                 &a[0]
        &a        int (*)[4]      n/a                   &a[0]
        *a        int             n/a                    a[0]

表达式a的类型为"4元素数组int" (int [4])。由于a不是sizeof_Alignof或一元&运算符的操作数,因此该表达式"衰减"为"指向int的指针",该表达式的值是第一个元素的地址。该表达式的结果与&a[0]完全等价。
表达式&a的类型为"指向4元素数组int的指针"(int (*)[4])。在这种情况下,a是一元&运算符的操作数,因此衰减规则不适用。
所有表达式 a&a&a[0] 都产生相同的值(即 a 的第一个元素的地址),但表达式的类型不同(这可能会影响值的表示方式)。a&a[0] 的类型是 int *,而 &a 的类型是 int (*)[4]
类型对于指针算术等事情很重要。假设以下声明:
int a[4] = {0, 1, 2, 3};
int *p = a;
int (*ap)[4] = &a;

变量 pap 最初指向同一个地址。然而,表达式 p + 1 将产生紧随 p 所指向的下一个 int 对象的地址(即 &a[1]),而 ap + 1 将产生下一个由 a 指向的 4 元素 int 数组 的地址。

这正是数组下标运算的工作原理 - 表达式 a[i] 被计算为 *(a + i)。给定起始地址 a,偏移 i 个对象(不是字节)并解引用结果。

这就是为什么在某些赋值中会出现错误的原因 - 类型 int *int (*)[4] 不是 兼容的。首先,它们不必以相同的方式表示(尽管在您可能使用的任何系统上它们都将是),并且在使用指针算术时行为不同。

指向不同类型的指针本身就是不同的类型,通常不能互换使用。


几乎所有类型都可以,但函数不能返回数组或函数类型,也不能有函数类型的数组。因此,对于 T (*a)[N]T 不能是函数类型;对于 T (*f)()T 不能是函数或数组类型。


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