一个关于c语言的指针移动问题
背景
在近期聊天的时候,有一个人(这个人嵌入式水平非常高,大家一般叫他 嵌入式之神)发了一个选择题说自己不会,我也看了看。
题目
单选题 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: &a 和 a 的区别
这是理解这道题的核心。
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);
&a:获取整个数组a的地址。&a + 1:对这个指向整个数组的指针加1,使其指向数组a内存区域之后紧邻的位置。可以想象成它指向了a[5]这个虚拟元素的地址。(int *):这是一个强制类型转换。它将&a + 1这个int (*)[5]类型的地址,转换成了一个普通的int *类型的指针。- 赋值后,
ptr就成了一个int *指针,它指向了数组a末尾的下一个内存位置。
详细分析 printf 语句
printf 会计算所有参数的值,然后进行输出。
计算第一个要打印的值
*(a + 1):a衰变为指向a[0]的指针。a + 1指向数组的第二个元素a[1]。*(a + 1)对该地址解引用,得到a[1]的值,也就是2。
计算第二个要打印的值
*(ptr - 1):- 我们已经知道
ptr指向数组a末尾的下一个位置(即a[4]之后的位置)。 ptr是一个int *类型的指针,所以ptr - 1会将指针地址向前移动一个sizeof(int)的大小。- 这使得
ptr - 1正好指向了数组的最后一个元素a[4]。 *(ptr - 1)对该地址解引用,得到a[4]的值,也就是5。
- 我们已经知道
总结
因此,printf 语句最终会打印出 2 和 5,格式为 %d %d,所以输出是 2 5。
