通过实际例子比较数组和指针的相似之处和不同之处

8

给定以下代码:

#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);,为什么会得到相同的输出结果呢?

非常感谢您提前的帮助。

5个回答

9
在大多数情况下,数组类型的表达式将被隐式转换为“T类型指针”,并且其值将被设置为指向数组的第一个元素。但是,在以下情况下,此规则不适用:当数组是&和sizeof运算符的操作数时;或者该数组是字符串字面值,用于初始化声明中的另一个数组。
那么这些规则如何与您的代码相关呢?
在这一行代码中:
scanf("%d", &a);

您正在对数组应用&运算符。这会抑制从“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()“成功”的原因;你传递了正确的指针,但编译器发出警告,因为表达式的类型与函数所需的不匹配。如果你这样写:
scanf("%d", a);

如果您使用的是scanf()函数,那么a的类型将被视为int *,因此您不会收到任何警告。请注意,这与调用以下代码是相同的:

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

关于 b...
你明确地将 b 声明为指向整数的指针,并分配一块内存给它。当你对它应用 & 运算符时,你得到的是变量 b 的地址,其类型为 int **(因此是第二个警告),而不是 b 指向的地址。
expression      type          value         note
----------      ----          -----         ----
         b      int *         0x0040000     value contained in b
        &b      int **        0x0001000     address of b

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

scanf("%d", b);

5

数组a被放置在栈上,第一个元素的地址与a的地址相同。&a[0]和&a是同一个地址。

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

这就是为什么第一个scanf和printf可以工作,但第二个不能的原因。

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


2
在第一个scanf中,您传递了一个指向数组的引用。在C中,数组是指向分配类型的内存块的指针,对于你的情况是int *,表达式如a [0]被转换为*(a + 0)(这顺便引起了有趣的变体0 [a],实际上也可以编译)。该数组在堆栈上分配。第二个数组在堆上分配,并且堆栈包含指向该数组的指针变量。
在两种情况下,您都不是传递到第一个数组条目的指针,而是传递到数组和指向数组的指针。
第一个scanf覆盖了数组,因为它分配在堆栈上,所以您的值最终(幸运地)在数组中。
第二个scanf覆盖了指向数组的指针,从而改变了指向可能不存在于数据段中的内存地址的指针。这导致执行错误。

1
在您的情况下,发生的情况是您使用 & 运算符将变量 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) );
    }
}

在这里,您可以使用指针读取向量。此外,使用指针算术运算,您可以迭代向量的内存地址。


1

这很正常...

首先,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])会崩溃。


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