【问题标题】:Why doesn't scanf() treat array and &array the same?为什么 scanf() 不将数组和 &array 视为相同?
【发布时间】:2014-08-25 19:48:02
【问题描述】:

我写了这个简单的程序

#include <stdio.h>

int main(void)
{
    int array[10];

    printf("sizeof(array):  %lu\n", sizeof(array));
    printf("sizeof(&array): %lu\n", sizeof(&array));

    printf("array:          %p\n", array);
    printf("&array:         %p\n", &array);

    printf("value:          ");
    scanf("%d", array); // 1
    //scanf("%d, &array"); // 2
}

输出:

sizeof(array):  40
sizeof(&array): 8
array:          0x7fffaab6f480
&array:         0x7fffaab6f480
value:          10

当我使用 1 时它会编译。但是,当我使用 2

我收到此编译警告

warning: format ‘%d’ expects argument of type ‘int *’, but argument 2 has type 
‘int (*)[10]’ [-Wformat=]
  scanf("%d", &array);
  ^

虽然array&amp;array 具有相同的值,但它们在我的系统上具有不同的大小(分别为408)。

另一方面,这段代码

int array[10];
fread(&array, x, y, z);

编译并完美运行

int array[10];
fread(array, x, y, z);

我在警告消息中注意到&amp;array 的类型为int (*)[10]。这是什么意思?

我也相信fread() 没有问题,因为它接受void *,但是scanf() 呢?它如何区分指针类型?

为什么不将array 视为&amp;array,即使它们实际上具有相同的值?

【问题讨论】:

标签: c arrays pointers scanf fread


【解决方案1】:

数组衰减为指针,因此使用数组作为参数与将指针传递给第一个元素相同。

当您执行&amp;array 时,您会得到一个指向数组的指针(int (*)[10] 事物),而不是指向整数的指针。

【讨论】:

  • 我想过这个,但后来我问自己&amp;array是否被视为指向数组的指针而array被视为指向该数组中第一个int的指针,那为什么是sizeof(&amp;array) 评估为 8sizeof(array) 评估为 40?如果它们都可以互换使用,那么为什么我在与scanf() 一起使用时会收到警告,而不是与fread() 一起使用?
  • 因为sizeof 不是函数。这里数组不会衰减。
  • 所以它有时会衰减,有时不会衰减?那我怎么知道它在特定情况下会衰减呢?
  • @KareemMesbah 参见例如this answer from yesterday 一些。
  • @KareemMesbah 理论上array&amp;array&amp;array[0] 是不同的,实际上它们在现代PC 上都是同一个指针。重要的是要知道它们的行为可能不同,并且知道何时应该使用其中一种。
【解决方案2】:

正如您所建议的,两者在语法上都是正确的 C,并且做的事情完全相同。然而,一个比另一个更正确。也就是说,两者都不是完美的。

要适当迂腐,你应该有这个:

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

.. 因为这是一个指向整数的指针,array[0],以及您真正想要的。

实际上,您可以将 anything 传递给scanf(如果您忽略文档),编译器应该会接受它(滥用标准传统上不会触发错误)。如果你传递了一些无效的东西,那么你的程序预计会在运行时失败(或者不会,如果你很幸运——未定义的行为就是这样)。

但是,您的编译器试图提供帮助并为您解释 scanf 格式字符串,以确保您做了正确的事情,但如果您仔细观察,它不是错误,它一个警告。如果您愿意,您可以随意忽略它。

fread 没有出错的原因是因为该函数不接受整数,而是接受 void *,并且两种形式都可以很容易地转换为该类型。

【讨论】:

  • 两者都是无效有效的 C。scanf 需要一个指向整数的指针,而不是指向指针的指针。
  • 好的,它是有效的 C 语言scanf 被定义为一个可变参数函数,它将接受你给它的任何垃圾。这不一定是对 library 标准的有效使用。
  • “有效 C”通常表示“不会导致未定义行为的 C”。不仅仅是“不给出编译器错误”。
  • 我认为区别在于语法上有效的代码和(也)语义上有效的代码。
  • @ams:如果参数的实际类型与转换说明符 (%d) 所需的类型不匹配,则行为未定义。我认为一个 C 程序到达一个调用未定义行为的语句不是“有效的 C”。
【解决方案3】:

对于scanfprintf 系列,您必须传递他们期望的确切类型(在应用默认参数提升之后-但这与本示例无关)。

这是因为参数对应于原型中的...,这意味着编译器不会尝试将您的参数转换为预期的类型。如果你使用了错误的类型,你只会得到未定义的行为。

这发生在第二种情况scanf("%d", &amp;array)。正如您的示例所示,&amp;array not 的类型为 int *,因此行为未定义。

实际上,如果您的编译器对int * 使用与int (*)[10] 相同的表示形式,那么它可能仍然有效,所有现代编译器都使用AFAIK。但是,当然,您不应该依赖未定义的行为,尤其是当有可用的简单修复时。

fread 示例中,它匹配原型参数void *。因此编译器将您的参数转换为void *。由于数组的第一个元素与数组本身位于相同的内存地址(void *)&amp;array == (void *)array,即使array&amp;array 具有不同的类型(甚至可能有不同的表示),它们都指向相同的内存地址.

【讨论】:

    【解决方案4】:

    首先,您的程序编译得非常好 - 警告 不是错误。

    警告信息告诉您格式和类型不匹配:“%d”格式需要int *,但您发送了&amp;array,其类型为int (*)[10]


    arrayint 的 10 长度数组。它的类型是int [10]。但是数组可以转换成指针,即&amp;array[0]

    当您尝试将数组传递给函数时,您实际上传递了一个指针&amp;array[0]。所以这两个代码

    printf("%d", array);
    scanf("%d", array);
    

    这是另一个代码

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

    都是一样的。

    现在让我们看看另一件事,&amp;array。它的类型是int (*)[10],即指向数组的指针

    当然,&amp;array[0]&amp;array 的实际值是相同的,但是它们有不同的类型。所以它们是不同的东西,因为'A'65 是不同的。

    虽然看起来不错,因为值相同,但我认为这不是一个好习惯 - 使用 array&amp;array[0]

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-08-14
      • 1970-01-01
      • 1970-01-01
      • 2014-01-25
      • 1970-01-01
      • 2012-07-20
      • 2012-06-03
      • 1970-01-01
      相关资源
      最近更新 更多