一个关于c语言的指针移动问题

Shi Yi2025/10/13baguc

背景

在近期聊天的时候,有一个人(这个人嵌入式水平非常高,大家一般叫他 嵌入式之神)发了一个选择题说自己不会,我也看了看。


📝 题目

单选题 C 语言

28. 下面程序的输出是什么?

int main(void) {
    int a[5] = {1, 2, 3, 4, 5};
    int *ptr = (int *)(&a + 1);
    printf("%d %d", *(a + 1), *(ptr - 1));
    return 0;
}
  • A 2 1
  • B 2 5
  • C 1 2
  • D 5 2

TIP

我这里直接把答案写出来了,答案是 B,大部分人可能会去选择 A,可能也些人比较扎实可以选择正确。


🔍 解析

好的,我们来详细解析这段 C 语言代码。

代码分步解析

让我们逐行分析代码,特别是涉及指针操作的部分:

int main(void) {
    // 1. 声明并初始化一个包含 5 个整数的数组
    int a[5] = {1, 2, 3, 4, 5};

    // 2. 声明一个整型指针 ptr,这是最关键的一行
    int *ptr = (int *)(&a + 1);

    // 3. 打印两个整数的值
    printf("%d %d", *(a + 1), *(ptr - 1));

    return 0;
}

关键点 1:&aa 的区别

这是理解这道题的核心。

  • a:在大多数表达式中(包括 *(a + 1) 中),数组名 a 会"衰变"为一个指向数组第一个元素的指针。它的类型是 int *
  • &a:这是对整个数组取地址。它是一个指向"包含 5 个整数的数组"的指针。它的类型是 int (*)[5]

WARNING

很多人误解为这样,如果衰变了,a 的类型就是 int,那难道 &a 的类型是 int ** 吗?很显然这里有问题。

a 的真实类型自始至终都是 int[5](一个包含 5 个整数的数组)。它不是指针。在大多数使用到数组名的表达式中,编译器会自动将数组名 a 隐式地转换为一个指向其第一个元素的指针。这个转换后的指针,其类型是 int *,其值是 a[0] 的地址。


关键点 2:指针的算术运算

指针进行加减运算时,移动的步长取决于指针指向的类型的大小。

表达式类型+1 移动的字节数
aint *sizeof(int),指向下一个整数 a[1]
&aint (*)[5]sizeof(int[5]),整个数组的大小

详细分析 int *ptr = (int *)(&a + 1);

  1. &a:获取整个数组 a 的地址
  2. &a + 1:对这个指向整个数组的指针加 1,使其指向数组 a 内存区域之后紧邻的位置。可以想象成它指向了 a[5] 这个虚拟元素的地址
  3. (int *):这是一个强制类型转换。它将 &a + 1 这个 int (*)[5] 类型的地址,转换成了一个普通的 int * 类型的指针
  4. 赋值后,ptr 就成了一个 int * 指针,它指向了数组 a 末尾的下一个内存位置

详细分析 printf 语句

printf 会计算所有参数的值,然后进行输出。

1. 计算第一个要打印的值 *(a + 1)

  • a 衰变为指向 a[0] 的指针
  • a + 1 指向数组的第二个元素 a[1]
  • *(a + 1) 对该地址解引用,得到 a[1] 的值,也就是 2

2. 计算第二个要打印的值 *(ptr - 1)

  • 我们已经知道 ptr 指向数组 a 末尾的下一个位置(即 a[4] 之后的位置)
  • ptr 是一个 int * 类型的指针,所以 ptr - 1 会将指针地址向前移动一个 sizeof(int) 的大小
  • 这使得 ptr - 1 正好指向了数组的最后一个元素 a[4]
  • *(ptr - 1) 对该地址解引用,得到 a[4] 的值,也就是 5

✅ 总结

因此,printf 语句最终会打印出 25,格式为 %d %d,所以输出是:

2 5
Last Updated 7/19/2026, 1:58:07 PM