【问题标题】:What int (*ptr)[4] really means and how is it different than *ptr?int (*ptr)[4] 的真正含义是什么,它与 *ptr 有何不同?
【发布时间】:2020-09-24 08:11:52
【问题描述】:
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);) 的地址,您将看到该地址比 ' 的地址大 4 * sizeof(int) p'.

标签: c arrays pointers memory-address


【解决方案1】:

想象指针是激光指针,有不同的颜色(红色表示指向 int 的指针,绿色表示指向数组的指针,...)和变量是您可以使用正确的激光指针指向的东西,即,您不能使用绿色激光指针指向 char 变量。

好的,所以你有int a[4] 一个数组(4 个整数)。使用绿色指针指向它:int (*green)[4] = &a;...您还有一个 int (a[0]),您可以使用红色指针指向它:int *red = &a[0]; /* in most contexts 'a' by itself is converted to "address of first element": &a[0] is the same as a */

现在问你的色盲朋友指针指向哪里:)
就你的朋友而言,他们是平等的,指向同一个“地方”……但你欺骗了你的朋友! 编译器是色盲的,不喜欢被欺骗

【讨论】:

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

int (*ptr)[4] 的真正含义是什么,它与 *ptr 有何不同?”

首先,我们看一下声明本身:

int * ptr - ptrint * 类型 - 指向 int 的指针。

int (*ptr)[4] - ptr 的类型为 int (*)[4] - 指向四个 int 的数组的指针。

种类不同。


我的意思是,如果p = &a = 0x7ff... 有效,为什么不p = a = 0x7ff..

挑剔的旁注:这些表达式不会被编译,但我知道这只是说明上下文的示例。

在 C 中,数组类型的表达式能够衰减为指向数组第一个元素的指针。

引自 C18 标准 ISO/IEC 9899:2018:

"除非是 sizeof 运算符的操作数,或一元 & 运算符,或者是用于初始化数组的字符串字面量,否则类型为 "array of type" 的表达式将转换为类型为 " 的表达式指向类型的指针”,指向数组对象的初始元素,不是左值。如果数组对象有寄存器存储类,则行为未定义。”

来源:C18,§6.3.2.1/3

但是由于& 运算符用于aa 不会衰减为指向a 的第一个元素的指针。相反,& 应用于数组本身并生成指向整个数组的指针(类型为int (*)[4])。

这是一种语法类型差异/不匹配,int *int (*)[4],当然两者都指向内存中的相同地址。

编译器有义务对任何类型不匹配进行诊断,因为它是语法违规。

当然,两者的地址相同,但赋值时的类型不兼容会有所不同。

【讨论】:

    【解决方案3】:

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

    现在,

    • p = a 失败是因为 a 在分配时衰减为指向 int 的指针,而 p 指向不同的类型。阅读更多关于腐烂的信息:What is array to pointer decay?
    • ptr = &a 失败,因为 ptr 实际上是一个指向 int 的指针;它与p 的类型不同。在同一行声明多个变量通常会令人困惑,因为并非所有语法都适用于您声明的所有内容;最好将这些定义拆分为单独的行。

    【讨论】:

    • 好的,所以它的大小不同。尽管如此,当所有的赋值都只是起始变量的内存地址时,为什么其中一些赋值会出错?
    【解决方案4】:

    这是不同类型的问题,类型是编译器中存在但编译后的二进制文件中不存在的概念。这就是为什么即使这两种指针类型实际上指向同一个地址也会出现编译器错误的原因。

    您可以将int (*p)[4]=&arr; 视为指向整个数组的指针,而int* ptr=arr;指向数组中第一个元素的指针

    通常在表达式中使用时,数组名称“衰减”为指向第一个元素的指针。这就是我们写int* ptr=arr; 时发生的情况——它100% 等同于写int* ptr = &arr[0];

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

    除非它是sizeof 运算符的操作数,或一元& 运算符,或者是用于初始化数组的字符串字面量,否则类型为“array of type”的表达式是 转换为类型为“pointer to type”的表达式,该表达式指向 数组对象,不是左值。

    正如我们所见,& 运算符是规则中的一个特殊例外。这意味着在&arr 的情况下,arr 部分不会衰减。所以我们应该得到一个指向数组类型的指针,而不仅仅是第一个元素。这就是int (*p)[4] 适合的地方。

    当然,“指向整个数组的指针”会同时指向第一项的地址,因为那是数组开始的地址。如果我们printf("%p\n", p),无论我们传递数组指针还是指向第一个元素的指针,我们都会得到相同的地址。

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

    【讨论】:

      【解决方案5】:

      我试图了解a&a&a[0] 是什么。

      在 C 数组中衰减为指针。所有这些指针都引用相同的内存位置(数组的第一个元素)。唯一的区别是类型。

      a&a[0] 具有数组元素的类型(在本例中为 int

      &a 是指向元素数组类型的指针(在本例中为 4 个整数的数组)。

      【讨论】:

        【解决方案6】:

        以下是基本规则:

        对于任何1类型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 或一元& 运算符的操作数,或者是用于在声明中初始化字符数组的字符串文字,表达式T”类型的“N 元素数组”将被转换(“衰减”)为“指向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 的类型为“int 的四元素数组”(int [4])。 a 不是sizeof_Alignof 或一元& 运算符的操作数,因此表达式“衰减”为“指向int”的指针,表达式的值是第一个元素。这个表达式的结果完全等同于&a[0]

        表达式&a 的类型为“指向int 的4 元素数组的指针”(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 指向的任何对象(IOW,&a[1])之后产生下一个 int 对象的地址,而 ap + 1 将产生int 的下一个 4 元素数组a 之后。

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

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

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


        1. 嗯,几乎所有的 - 函数都不能返回数组或函数类型,而且你不能有函数类型的数组,所以对于T (*a)[N]T不能是函数类型,对于@987654380 @T 不能是函数或数组类型。

        【讨论】:

          【解决方案7】:

          通过这些讨论,两个答案都已得到明确。我想总结一下:

          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 OR ptr=&a[0] 的方式分配(表示分配的空间是一个整数)。而如果 ptr 被声明为数组的地址,则 ptr = &a[0] pr ptr = a 被编译器解释为这个 ptr 得到一个整数的地址,这是不正确的,因为这里的 ptr 表示一个整数的地址大批。在这种情况下,它不应该保存整数的地址。因此, p=&a 在语法上看起来对编译器非常正确。因此,这是它接受的唯一选项。

          :)

          【讨论】:

          • 由于运算符优先规则,您需要将(*ptr)[4]括起来。
          • 同意。但这里的问题是关于 *ptr 的内容。在一种情况下,它保存地址(int),在另一种情况下,它保存地址范围(数组),其中 ptr 实际上保存这些地址范围的一种标识符,并且编译器被编程为仅根据这些概念接受代码。
          • 这只是一个 C 语法注释。 (但语法很重要,因为它定义了我们所说的内容。如果您说int *ptr[4],您定义了一个您索引的实体 ptr,从而产生一个指向 int 的指针;换句话说,您谈论的是 指针数组,而不是关于指向数组的指针。 指向数组的指针需要首先被取消引用,然后被索引。这种早期的取消引用由方括号强制执行。)顺便说一下,所有指针“持有”只是一个数字,地址;它们中没有存储类型信息。所有类型信息在翻译过程中只在编译器的“头脑”中。
          • 想一想,所有类型(在 C 中)都是如此。它在 Java 和 C# 等语言中有所不同,其中每个对象都携带有关自身的信息,这些信息可以在 runtime 通过反射进行检查。在 C 中,没有像 Invalidcastexception 这样的运行时机制。数组也不存储它的长度;只有在编译时,编译器才会强制执行某些类型检查。
          猜你喜欢
          • 2022-01-15
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多