给出以下代码:
#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)替换为?
,为什么会获得相同的输出?非常感谢提前
答案 0 :(得分:10)
在大多数情况下,数组类型的表达式将从“N元素数组T”隐式转换为“指向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);
您将不会收到任何警告,因为a
的类型将被视为int *
,这是scanf()
所期望的。请注意,这与调用
scanf("%d", &a[0]);
至于b
......
您明确地将b
声明为指向int的指针,并为其分配一块内存。当您将&
运算符应用于它时,您获得的是变量 b
的地址,其类型为int **
(因此是第二个警告),而不是b
指向的地址。
expression type value note
---------- ---- ----- ----
b int * 0x0040000 value contained in b
&b int ** 0x0001000 address of b
对于这种情况,您只需传递未修饰的b
:
scanf("%d", b);
答案 1 :(得分:5)
数组a放在堆栈上,第一个元素的地址与a的地址相同。 &amp; a [0]和&amp; a是相同的地址
数组b分配有malloc,存储的地址在堆上,而指针b的地址在堆栈上。 &amp; b [0]与&amp; b 的地址不同。
这就是为什么第一个scanf和printf工作但不是第二个。
C-FAQ更详细地解释了它。
答案 2 :(得分:2)
在第一次扫描中,您传递对数组的引用。在C数组中是指向已分配类型的内存块的指针,在您的情况下为int *
,并且类似a[0]
的表达式会转换为*(a + 0)
(btw会产生有趣的变体{{它将实际编译。)此数组在堆栈上分配。第二个数组在堆上分配,堆栈包含指向该数组的指针变量。
在这两种情况下,您都不会将指针传递给第一个数组条目,而是分别传递给数组和指向数组的指针。
你的第一个scanf覆盖了数组是什么,因为它在堆栈上分配,你的值最终(运气好)在数组中。
您的第二个scanf会覆盖指向数组的指针,从而将指针更改为数据段中可能不存在的内存地址。这会导致执行错误。
答案 3 :(得分:1)
这是正常的......
首先,scanf需要一个指针。 “a”和“b”已经成为指针! 所以:
/* 1 */
scanf("%d", a);
printf("%d\n", a[0]);
/* 2 */
scanf("%d", b);
printf("%d\n", b[0]);
会工作。
通常/ * 1 * /不起作用。但是gcc将“&amp; a”改为“a”,因为“&amp; 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
你无法接受一个地址。但是b是类型指针的“正常变量”,因此你可以用“&amp; b”来取它的地址。
开/ * 2 * /你将用户输入的值放在b中,因此* b(或b [0])将崩溃,除非用户输入有效的可读存储器地址。
答案 4 :(得分:1)
在你的情况下,发生的事情是你传递的变量a和b与&amp;操作符到scanf函数。该运算符的作用是“询问”变量的内存地址,并将该地址传递给scanf函数。但是,由于你的两个变量都是指针,它们确实是一个内存地址,因此当你传递&amp; a或&amp; 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.
所以如果你想传递scanf变量x,但是使用指针,你需要传递存储在其中的x地址
scanf("%d", ptr);
只是为了说明另一个指针和向量的例子
int main
{
int vet[5];
int *ptr;
ptr = vet;
for(int i = 0; i < 5; ++i)
{
scanf("%d", (ptr+i) );
}
}
在这里,您可以使用指针读取矢量。此外,使用指针算术,您可以迭代向量的内存地址。