14

我最近在向同事解释原因时让自己感到尴尬

char a[100];
scanf("%s", &a); // notice a & in front of 'a'

非常糟糕,稍微好一点的方法是:

char a[100];
scanf("%s", a); // notice no & in front of 'a'  

好的。对于每个准备告诉我为什么出于安全原因无论如何都不应该使用 scanf 的人:放轻松。这个问题实际上是关于“&a”与“a”的含义。

问题是,在我解释了它为什么不起作用之后,我们尝试了它(使用 gcc)并且它起作用了 =))。我跑了一个快

printf("%p %p", a, &a);

它会打印两次相同的地址。

任何人都可以向我解释发生了什么事吗?

4

6 回答 6

18

好吧,&a案子应该很明显了。您完全按照预期获取数组的地址。 a有点微妙,但答案是a 数组。并且正如任何 C 程序员都知道的那样,数组倾向于在最轻微的刺激下退化为指针,例如在将它作为函数参数传递时。

所以scanf("%s", a)需要一个指针,而不是数组,所以数组退化为指向数组第一个元素的指针。

当然scanf("%s", &a)也可以,因为那是数组的明确地址。

编辑:哎呀,看起来我完全没有考虑 scanf 实际期望的参数类型。这两种情况都会产生一个指向相同地址但类型不同的指针。(指向字符的指针,而不是指向字符数组的指针)。

我很乐意承认我对省略号 (...) 的语义知之甚少,我总是像瘟疫一样避免这种情况,所以看起来转换为 scanf 最终使用的任何类型都可能是未定义的行为。阅读评论和 litb 的答案。你通常可以相信他能把这些东西做好。;)

于 2009-01-30T16:52:01.863 回答
11
于 2009-01-30T17:03:29.143 回答
6

自从我用 C 编程以来已经有一段时间了,但这是我的 2c:

char a[100]不会为数组的地址分配单独的变量,因此内存分配如下所示:

 ---+-----+---
 ...|0..99|...
 ---+-----+---
    ^
    a == &a

为了比较,如果数组是 malloc 的,那么指针有一个单独的变量,并且a != &a.

char *a;
a = malloc(100);

在这种情况下,内存如下所示:

 ---+---+---+-----+---
 ...| a |...|0..99|...
 ---+---+---+-----+---
    ^       ^
    &a  !=  a

K&R 第 2 版。p.99 很好地描述了它:

索引和指针算法之间的对应关系非常密切。根据定义,数组类型的变量或表达式的值是数组元素零的地址。因而赋值后又具有相同的pa=&a[0]; paa。由于数组的名称是初始元素位置的同义词,因此赋值pa=&a[0]也可以写为pa=a;

于 2009-01-30T17:30:47.403 回答
5

AC 数组可以隐式转换为指向其第一个元素的指针(C99:TC3 6.3.2.1 §3),即在很多情况下a(具有 type char [100])的行为方式与&a[0](具有 type char *)相同。这解释了为什么a作为参数传递会起作用。

但是不要开始认为情况总是如此:数组和指针之间存在重要差异,例如关于赋值,sizeof以及我现在想不到的其他任何东西......

&a实际上是这些陷阱之一:这将创建一个指向数组的指针,即它具有类型char (*) [100](而不是 char **)。这意味着&a并且&a[0]将指向相同的内存位置,但将具有不同的类型。

据我所知,这些类型之间没有隐式转换,也不保证它们也具有兼容的表示形式。我能找到的只有 C99:TC3 6.2.5 §27,它没有太多关于指向数组的指针:

[...]指向其他类型的指针不需要具有相同的表示或对齐要求。

但也有 6.3.2.3 §7:

[...]当指向对象的指针转换为指向字符类型的指针时,结果指向对象的最低寻址字节。结果的连续增量,直到对象的大小,产生指向对象剩余字节的指针。

所以演员(char *)&a应该按预期工作。实际上,我在这里假设数组的最低寻址字节将是其第一个元素的最低寻址字节 - 不确定这是否得到保证,或者编译器是否可以自由地在数组前面添加任意填充,但是如果是这样,那就太奇怪了……

无论如何,要使其正常工作,&a仍然必须强制转换为char *(或void *- 标准保证这些类型具有兼容的表示形式)。问题是除了默认参数提升之外,不会有任何转换应用于变量参数,即您必须自己显式地进行转换。


总结一下:

&a是 类型char (*) [100],它可能具有与 不同的位表示char *。因此,程序员必须进行显式转换,因为对于变量参数,编译器无法知道它应该将值转换为什么。这意味着只会进行默认参数提升,正如litb 所指出的那样,它不包括转换为void *. 它遵循:

  • scanf("%s", a);- 好的
  • scanf("%s", &a);- 坏的
  • scanf("%s", (char *)&a);- 应该可以
于 2009-01-30T17:20:43.833 回答
4

抱歉,有点跑题了:

这让我想起了大约 8 年前我在全职编写 C 时读到的一篇文章。我找不到这篇文章,但我认为它的标题是“数组不是指针”或类似的东西。无论如何,我确实遇到了这个C 数组和指针常见问题解答,这很有趣。

于 2009-01-30T16:57:20.077 回答
0

char [100]是 100 个相邻 的复数类型charsizeof等于 100。

被强制转换为指针 ( (void*) a),此变量产生第一个的地址char

引用这种类型的变量 ( &a) 产生整个变量的地址,而这又恰好是第一个变量的地址char

于 2009-01-30T17:06:34.473 回答