一个关于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

我这里直接把答案写出来了,答案是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的类型就是int,那难道&a的类型是int**吗?很显然这里有问题
  • &a:这是对整个数组取地址。它是一个指向“包含5个整数的数组”的指针。它的类型是 int (*)[5]a 的真实类型自始至终都是 int[5](一个包含5个整数的数组)。它不是指针。在大多数使用到数组名的表达式中,编译器会自动将数组名 a 隐式地转换为一个指向其第一个元素的指针。这个转换后的指针,其类型是 int *,其值是 a[0] 的地址。

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

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

  • 对于 a (类型为 int *),a + 1 会将地址移动 sizeof(int) 个字节,从而指向下一个整数 a[1]
  • 对于 &a (类型为 int (*)[5]),&a + 1 会将地址移动 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/14/2026, 3:15:09 PM