(C初学者警报)
我想从用户读取一些整数并将它们存储在数组中。所以:
int main (void)
{
int i, num, cont = 0;
int arre[10];
for (int i=0;i<5;i++)
{
scanf("%d", arre[i]);
etc.
当我运行此操作时,我在OSX上收到了Segmentation Fault 11。如果我用Valgrind运行它,当我输入第一个整数时会出现问题,它告诉我:
==1610== Command: ./ArraysAndPointers
==1610==
2
==1610== Use of uninitialised value of size 8
==1610== at 0x18F0BA: __svfscanf_l (in /usr/lib/system/libsystem_c.dylib)
==1610== by 0x18718A: scanf (in /usr/lib/system/libsystem_c.dylib)
==1610== by 0x100000F2D: main (ArraysAndPointers.c:11)
==1610==
==1610== Invalid write of size 4
==1610== at 0x18F0BA: __svfscanf_l (in /usr/lib/system/libsystem_c.dylib)
==1610== by 0x18718A: scanf (in /usr/lib/system/libsystem_c.dylib)
==1610== by 0x100000F2D: main (ArraysAndPointers.c:11)
==1610== Address 0x0 is not stack'd, malloc'd or (recently) free'd
如果我在arre [i]前添加&amp; ,则可以解决问题。但我不知道为什么。我正在努力解决这个事实,即我正在读取一个整数,但在数组中存储(显然)内存地址。然而,当我检查它在结果数组中出现的值时,它本身就是int,而不是内存地址。为什么会这样?
注意:我基本上都在努力掌握指针/内存地址及其与数组,char *等的关系(参见my other questions),尽管我们已经与不同的提供商进行了多次C培训模块,并在线观看了各种解释,我还没有遇到过一个可以为我明确指出这个概念的人。特别是,我有兴趣知道何时和为什么指针。如果有人能为我推荐一个好的参考/视频/教程/文章,我将非常感激。
答案 0 :(得分:5)
为了便于解释,让我们将i
替换为0。
scanf("%d", arre[0]);
此代码转到数组,查找第一个元素,并发现它是17(或其他),因此它将17作为第二个参数传递给scanf()
。但是scanf()
期待一个指针,所以当它看到17并且最终导致你的应用程序崩溃时会非常混乱。
scanf("%d", &arre[0]);
此代码计算数组中第一个元素的位置,并将该指针传递给scanf()
。 scanf()
愉快地将值写入该指针所寻址的内存中。
答案 1 :(得分:1)
scanf()
格式说明符的 %d
期望int *
作为其第二个参数。
因此,您需要提供所需的标准,以提供存储扫描值的变量的地址。
男子说:
d
匹配可选的带符号十进制整数;下一个指针必须是 指向int的指针。
回答关于指针的扩展问题:
任何好的C书都会详细解释为什么需要指针。简单地说,你需要一些内存来存储你的扫描值。在这种情况下,它是数组,您需要通过提供需要存储由&arr[i]
答案 2 :(得分:1)
scanf()方法需要2个参数:
C语言只能传递参数按值运行。您无法告诉函数在参数中存储值,并且参数将保留此值(从技术上讲,参数将复制到the stack
- LIFO队列 - 并在函数结束时从堆栈中删除)。
因此,如果您在参数(例如int)中存储值,该值将在函数末尾丢失。
如果你想保留这个值,因为你无法传递变量本身,你传入主内存中变量的内存地址(即&amp; var) (the heap
)而不是堆栈。它将由函数在类型( int * )的参数中接收,这意味着what is designed by this memory address is an int variable
。
因此,使用此地址(在堆栈上传递),可以修改主内存中的内容(堆),并且即使在堆中,也会保留堆中此地址写入的值。函数结束,因为清空堆栈不会清空堆。
通过编写:*a = <my int>
,将值存储在地址a的变量中(例如int * a:a是指向int的指针)。
有关信息,C数组变量实际上是指向数组第一个元素的指针(第一个元素的地址):arre is the same value as &arre[0]
arre[n]
是*(arre + n)
:存储在(数组的地址加上n个元素大小的偏移量)的内容。
答案 3 :(得分:1)
C按值传递所有函数参数,这意味着函数定义中的形式参数是内存中与函数调用中的实际参数不同的对象。请看以下示例:
void swap( int a, int b ) { int tmp = a; a = b; b = tmp; }
void foo( void )
{
int x = 1, y = 2;
swap( x, y );
printf( "x = %d, y = %d\n", x, y );
}
a
中的形式参数swap
在内存中的内容与x
中的foo
不同,因此我们对a
所做的任何更改都不会; t影响x
;致电swap
后,x
和y
的值保持不变。
为了让swap
函数更改x
和y
的值,我们必须将指针传递给这些变量:
void swap( int *a, int *b ) { int tmp = *a; *a = *b; *b = tmp; }
void foo( void )
{
int x = 1, y = 2;
swap( &x, &y );
printf( "x = %d, y = %d\n", x, y );
}
这一次,我们传递的{em>地址,而不是将x
和y
的值传递给swap
。 {1}}和x
。变量y
和a
分别是指针到b
和x
,因此写入表达式 { {1}}与写入y
相同(同样,写入表达式 *a
与写入x
相同。
描述它的简便方法是
*b
当您使用参数y
致电a == &x --> *a == x
b == &y --> *b == y
时,您将该数组元素的值传递给该函数。不幸的是,scanf
想要该元素的地址,以便它可以为其写一个新值。 arre[i]
试图将您发送的值解释为内存中对象的地址,因此是段错误。
这就是当您将scanf
表达式作为参数传递给scanf
时,需要使用&
运算符的原因。
请注意,如果参数已经是指针类型,那么不需要使用arre[i]
运算符。请注意,当您使用scanf
转换说明符读取字符串时,通常会传递一个数组参数,如下所示:
&
在这种情况下,%s
参数是从类型&#34; char input[81];
scanf( "%s", input );
&#34;的数组的表达式隐式转换而来的。表达式为&#34;指向input
&#34;的指针,表达式的值是数组中第一个元素的地址。在大多数情况下,数组表达式会“衰减”#34;指针表达式。作为初学者,这个会咬你几次。
答案 4 :(得分:1)
声明
scanf("%d", arre[i]);
会将[i]钻进一个地址。
这个地址没有分配,所以它是一个所谓的&#34;野指针&#34;。访问不属于您的地址是未定义的行为。如果它是受系统保护的地址,系统将终止进程并发出分段错误。
我很想知道何时以及为什么需要指针。
C函数始终按值调用,这意味着调用函数中的参数只是的副本调用者中的参数。修改参数不会影响参数。一个常见的例子是
void swap(int a, int b)
{
int tmp = a;
a = b;
b = tmp;
}
int main(void)
{
int x = 1, y = 10;
swap(x, y);
printf("x = %d, y = %d\n", x, y); // still origin value
return 0;
}
使用指针的一个原因是您需要修改参数。您可以声明(或定义)您的函数,如
void swap(int* a, int *b);
并调用
swap(&x, &y);
使用指针的另一种情况是当参数是一个大结构时,其副本需要太多的时间和空间。然后使用它的指针。有时使用const
限定符来保护它不被修改(但不保证)。
比较以下foo
和goo
。
struct Big { char dummy[1024]};
void foo(struct Big b);
void goo(const struct Big* b);