当将值赋给指针时,出现“Initialization from incompatible pointer type warning”警告

17

当我使用这段代码时,GCC会给我一个“初始化类型不兼容的指针”的警告(尽管代码正常工作并完成了它应该完成的任务,即打印数组的所有元素)。

#include <stdio.h>

int main(void)
{
    int arr[5] = {3, 0, 3, 4, 1};
    int *p = &arr;

    printf("%p\n%p\n\n", p);

    for (int a = 0; a < 5; a++)
        printf("%d ", *(p++));
    printf("\n");
}

然而,当我使用这部分代码时,没有任何警告被给出。

int main(void)
{
    int arr[5] = {3, 0, 3, 4, 1};
    int *q = arr;

    printf("%p\n%p\n\n", q);

    for (int a = 0; a < 5; a++)
        printf("%d ", *(q++));
    printf("\n");
}

这两个片段唯一的区别在于我将 *p = &arr 和 *q = arr 分配不同的值。

  • & 的作用是什么?
  • 为什么代码在这两种情况下执行并给出完全相同的输出?

1
数组类型与指向数组项的指针兼容(但会衰减成指向第一个元素的指针),因此在第二种情况下没有问题。 - spectras
1
在学习过程中,使用 gcc -std=c11 -pedantic-errors 来编译 C 代码是一个好习惯。这将防止您执行无效的 C 代码。 - Lundin
5个回答

24
  • &arr返回一个数组指针,是特殊的指针类型int(*)[5],它指向整个数组。
  • 在表达式中使用arr时(如int *q = arr;),它会“衰减”为指向第一个元素的指针。与int *q = &arr[0];完全等价。

在第一种情况下,您尝试将一个int(*)[5]赋值给一个int*。这些不兼容的指针类型,因此编译器会发出错误诊断信息。

事实证明,数组指针和指向第一个元素的int指针很可能在内部具有相同的表示形式和相同的地址。这就是为什么第一个例子“有效”,即使它不是正确的C语言代码。


那很有道理。谢谢! - Nathu
在第一种情况下,您试图将int()[5]赋值给int。这些是不兼容的指针类型,因此编译器会发出诊断消息。简单来说,这意味着您正在尝试将一个指向数组的指针赋值给一个指向单个整数的指针,这是不允许的。 - Whois7pi
@Whois7pi 在 C 语言中,唯一可以在没有强制类型转换的情况下相互赋值的指针类型是指向完全相同类型的指针(以及 void 指针和空指针的特殊情况)。 - Lundin

5

TL;DR 检查类型。

  • &arr 的类型为 int (*) [5]指向包含 5 个 int 元素的数组的指针)。
  • arr 的类型为 int [5],但并非总是如此。

引用 C11,第 §6.3.2.1 章节,(强调为本人添加)

除了作为 sizeof 运算符、_Alignof 运算符或一元 & 运算符的操作数,或者用于初始化数组的字符串字面量时,具有类型为“类型数组”的表达式将转换为具有类型为“类型指针”的表达式,该指针指向数组对象的初始元素,并且不是左值。

因此,

 int *q = arr;   // int[5] decays to int *, == LHS

并且

 int *q = &arr[0];   // RHS == LHS

相同,然而,

 int *q = &arr; // LHS (int *) != RHS (int (*) [5])

类型表达式不匹配。

现在,它可以工作,因为如Lundin的答案中已经提到的那样,数组变量的地址很可能与数组的第一个元素的地址相同,因此尽管类型不匹配,是相同的,所以这似乎可以工作。


不,&arr 的结果类型是 int(*)[5] - Lundin

4

以下是指向数组(开头)的方式(无警告),两种方式都可行:

int *q = arr;
/* OR */
int *q = &arr[0];

这个代码有点介于两者之间,会产生一个警告:

int *q = &arr;

1
第一个示例都没有指向数组,它们指向数组的第一个项目(第二个是通过直接取其地址获得的,第一个是通过数组退化获得的)。在许多情况下都会争论不休,但在问题的背景下可能是相关的。 - spectras

1
输出结果相同,因为arr[0]的地址与指向arr[]的指针字面上是等价的。任何指向arr[0]的指针都将其值设置为arr[0]的地址;这就是指针的作用。阅读有关指针及其与数组的关系的文章。有无数的教程可供参考,其中一些可能会以你的两种情况作为示例。

0

当数组作为左值在表达式中使用时,它会衰变(decays)成指向其第一个元素的指针。因此int *q = arr;用数组arr的第一个元素的地址初始化int指针q:一切都很好,没有任何警告。

但是&arr是一个数组的地址。它只能正确地用来初始化(或分配给)指向数组或相同大小的指针,或者指向一个未确定大小的数组的指针。你使用它来初始化指向int的指针(这是一种不同且不兼容的类型),编译器会对此发出警告。因为使用从不同类型的指针初始化的指针是根据标准的Undefined Behaviour(未定义行为)。

但在常见的实现中,任何类型的指针都具有相同的表示方式,即对象的第一个字节的地址。因此,即使标准不允许,指向任何类型的指针的指令int *p = arr;最终得到的p的值与正确的int *p = arr;相同。这解释了为什么您的程序仍然给出了预期的值。

顺便说一下,未定义行为并不禁止预期结果,只是不同的编译器可能会给出不同的结果,崩溃,提前无错误地结束,踢你的狗等(即使到目前为止没有编译器能够打中我的狗;-))


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