【问题标题】:Similarities and differences between arrays and pointers through a practical example数组和指针的异同通过一个实例
【发布时间】:2010-12-19 10:28:10
【问题描述】:

给定以下代码:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int a[1];
    int * b = malloc(sizeof(int));

    /* 1 */
    scanf("%d", &a);
    printf("%d\n", a[0]);

    /* 2 */ 
    scanf("%d", &b);
    printf("%d\n", b[0]); 

    return 0;
}

编译时得到如下警告(i686-apple-darwin9-gcc-4.0.1):

array.c: In function 'main':
array.c:9: warning: format '%d' expects type 'int *', but argument 2 has type 'int (*)[0u]'
array.c:14: warning: format '%d' expects type 'int *', but argument 2 has type 'int **'

但是,为什么在第二个printf中会出现执行错误,而对第一个printf却有效?

还有,为什么把第一个scanf换成scanf("%d", a);会得到同样的输出?

提前非常感谢

【问题讨论】:

    标签: c arrays pointers


    【解决方案1】:

    在大多数情况下,数组类型的表达式将从“T 的 N 元素数组”隐式转换为“指向 T 的指针”,并且其值将被设置为指向数组的第一个元素。此规则的例外情况是数组是 &amp;sizeof 运算符的操作数,或者数组是用于在声明中初始化另一个数组的字符串文字。

    那么这一切与你的代码有什么关系呢?

    排队

    scanf("%d", &a);
    

    您正在将&amp; 运算符应用于数组。这会抑制从“T 数组”到“T 指针”的隐式转换,并返回“T 数组指针”或T (*)[N] 类型的值(因此您的第一个警告)。现在事实证明,指向数组的指针的值和指向数组第一个元素的指针的值是相同的,它们只是类型不同。所以假设a在地址0x0001000:

    expression      type          value         note
    ----------      ----          -----         ----
             a      int *         0x0001000     implicitly converted to pointer
            &a      int (*)[N]    0x0001000     
         &a[0]      int *         0x0001000
    

    这就是为什么你第一次打电话给scanf()“有效”;您传递了正确的指针 value,但编译器抱怨,因为表达式的 type 与函数所期望的不匹配。你写了吗

    scanf("%d", a);
    

    您不会收到任何警告,因为a 的类型将被视为int *,这是scanf() 所期望的。请注意,这与调用相同

    scanf("%d", &a[0]);
    

    至于b...

    您将b 显式声明为指向 int 的指针并为其分配一块内存。当您将&amp; 运算符应用于它时,您得到的是int ** 类型的变量 b 的地址(因此是第二个警告),而不是b 的地址指着。

    expression      type          value         note
    ----------      ----          -----         ----
             b      int *         0x0040000     value contained in b
            &b      int **        0x0001000     address of b
    

    对于这种情况,您只需传递未修饰的b

    scanf("%d", b);
    

    【讨论】:

      【解决方案2】:

      数组a入栈,第一个元素的地址与a的地址相同。 &a[0] 和 &a 是同一个地址

      数组 b 是用 malloc 分配的,存储地址在堆上,而指针 b 的地址在栈上。 &b[0] 与 &b 不是同一地址

      这就是为什么第一个 scanf 和 printf 有效而第二个无效的原因。

      C-FAQ 对此进行了更详细的解释。

      【讨论】:

        【解决方案3】:

        在第一个 scanf 中,您传递对数组的引用。在 C 中,数组是指向已分配类型的内存块的指针,在您的情况下,int *a[0] 之类的表达式被转换为*(a + 0)(顺便说一句,这会产生有趣的变体0[a],它实际上会编译。 ) 这个数组是在栈上分配的。第二个数组分配在堆上,堆栈包含指向该数组的指针变量。

        在这两种情况下,您都不会将指针传递给第一个数组条目,而是分别传递给数组和指向数组的指针。

        您的第一个 scanf 会覆盖数组是什么,因为它是在堆栈上分配的,所以您的值最终会(幸运地)在数组中。

        您的第二个 scanf 会覆盖指向数组的指针,从而将指针更改为您的数据段中可能不存在的内存地址。这会导致执行错误。

        【讨论】:

          【解决方案4】:

          这很正常……

          首先,scanf 需要一个指针。 “a”和“b”已经是指针! 所以:

          /* 1 */
          scanf("%d", a);
          printf("%d\n", a[0]);
          
          /* 2 */ 
          scanf("%d", b);
          printf("%d\n", b[0]);
          

          会起作用的。

          通常 /* 1 */ 不应该工作。但是 gcc 将 "&a" 转换为 "a" 因为 "&a" 没有任何意义。

          printf("&a = %p\n", &a);
          printf("a = %p\n", a);
          printf("&b = %p\n", &b);
          printf("b = %p\n", b);
          
          &a = 0x7ffff6be67d0
          a = 0x7ffff6be67d0
          &b = 0x7ffff6be67c8
          b = 0xb0b010
          

          您不能获取 a 的地址。但是 b 是指针类型的“普通变量”,因此您可以通过“&b”获取它的地址。

          在 /* 2 */ 上,您将用户输入的值放入 b 中,因此,*b(或 b[0])将崩溃,除非用户输入有效的可读内存地址。

          【讨论】:

            【解决方案5】:

            在您的情况下,您正在将变量 a 和 b 与 & 运算符一起传递给 scanf 函数。该运算符所做的是“询问”变量的内存地址并将该地址传递给 scanf 函数。但是,由于您的两个变量都是指针,它们确实是一个内存地址,所以当您传递 &a 或 &b 时,您传递的是指针的内存,而不是它所拥有的内存地址。

            例子:

            int x;
            int *ptr;
            
            x = 10;
            

            假设 x 的内存地址是 1000。您将数字 10 存储在内存地址 1000。现在您这样做:

            ptr = &x;
            

            您将地址 1000 存储在指针中。但是 1000 除了是一个地址之外,本身就是一个数字,所以指针和 x 一样,仍然需要一个内存地址来存储该信息。假设指针内存位置是 1004。现在看例子:

            *ptr == 10;  //x content
            ptr == 1000 //x memory address
            &ptr == 1004 // ptr memory address. 
            

            所以如果你想将变量x传递给scanf,但是使用指针,你需要传递其中存储的x地址

            scanf("%d", ptr);
            

            只是为了说明指针和向量的另一个例子

            int main
            {
                int vet[5];
                int *ptr;
            
                ptr = vet;
            
                for(int i = 0; i < 5; ++i)
                {
                    scanf("%d", (ptr+i) );
                }
            }
            

            在这里您可以使用指针读取向量。此外,使用指针算法,您可以遍历向量的内存地址。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2016-10-27
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2013-12-20
              • 2023-03-21
              • 2012-08-14
              • 2021-12-24
              相关资源
              最近更新 更多