首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >指针终极挑战(5):这7道题能全对,指针就算真懂了!

指针终极挑战(5):这7道题能全对,指针就算真懂了!

作者头像
Extreme35
发布2025-12-23 18:19:34
发布2025-12-23 18:19:34
90
举报
文章被收录于专栏:DLDL

指针是C语言的灵魂,也是无数程序员的噩梦。今天带来7道经典指针题目,能全部答对的话,你的指针功底就算真正过关了!

前言:为什么指针如此重要?

指针是C语言最强大的特性,也是最具挑战性的概念。它直接操作内存,提供了无与伦比的灵活性,但同时也带来了复杂性和风险。 据统计,超过60%的C语言bug与指针使用不当有关! 这些题目大家可以先自己写一下,看看自己基础如何!!!


第一关:sizeof vs strlen 基础对决

本关简介:sizeofstrlen是初学者最容易混淆的两个概念。sizeof是编译时运算符关心内存布局strlen是运行时函数寻找字符串结尾。理解它们的区别是指针学习的第一个里程碑,也是避免内存越界的关键所在。 strlen函数的工作原理:strlen 函数从给定的内存地址开始,逐个字节向后计数,直到遇到第一个字符串结束符 '\0' (ASCII值为0)为止。

题目1:字符数组的陷阱

代码语言:javascript
复制
int main()
{
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", strlen(arr));
	printf("%d\n", strlen(arr + 0));
	printf("%d\n", strlen(*arr));
	printf("%d\n", strlen(arr[1]));
	printf("%d\n", strlen(&arr));
	printf("%d\n", strlen(&arr + 1));
	printf("%d\n", strlen(&arr[0] + 1));
	return 0;
}

关键问题: 代码中的 char arr[] = { 'a', 'b', 'c', 'd', 'e', 'f' }; 是一个字符数组,它不是字符串,因为它没有以 '\0' 结尾。 逐行详细解析:

  1. printf("%d\n", strlen(arr));
    • arr 是数组名,在此处代表数组首元素的地址,即 &arr[0](指向 'a'
    • strlen'a' 的地址开始,向后寻找 '\0' ,它会依次经过 'b', 'c', 'd', 'e', 'f',然后进入数组之后未知的内存区域。
    • 结果:这是一个典型的内存越界访问。strlen 会继续计数,直到在未知内存的某个位置偶然遇到一个 0'\0')。最终返回的数值是一个不确定的、很大的随机值。
  2. printf("%d\n", strlen(arr + 0));
    • arr + 0 在值上等于 arr,同样是数组首元素的地址。因此,它的行为与第一行完全一样。
    • 结果:返回与第一行相同的不确定的随机值。
  3. printf("%d\n", strlen(*arr));
    • *arr 是对首元素地址进行解引用,得到的是字符 'a',其ASCII码为 97
    • strlen 函数期望接收一个 const char*(内存地址),但现在我们传给它的是 97
    • 程序会将整数值 97 强制解释为一个内存地址(即地址 0x00000061
    • 结果:这是一次严重的非法内存访问。程序试图从地址 0x00000061 开始读取数据,这通常是一个未被映射的低地址区,会导致程序崩溃(Segment Fault)。
  4. printf("%d\n", strlen(arr[1]));
    • arr[1] 是数组的第二个元素,即字符 'b',其ASCII码为 98
    • 这与第三行的情况本质相同,只是传入了不同的整数值
    • strlen 被传入 98,并试图从地址 0x00000062 开始读取
    • 结果:同样是非法内存访问,导致程序崩溃(Segment Fault)。

重要提示:由于C语言的求值顺序,程序会在执行到第3或第4行时崩溃,后续的 printf 将没有机会执行。故结果展示时会注释掉这两行。

  1. printf("%d\n", strlen(&arr));
    • &arr 的类型是 char (*)[6],即「指向长度为6的字符数组的指针」
    • 它的值虽然和 arr 相同,都是数组的起始地址,但指针类型不同
    • strlen 的参数类型是 const char*&arr 会被隐式转换为 const char*
    • 结果:行为与第一、二行完全相同,返回一个不确定的随机值。
  2. printf("%d\n", strlen(&arr + 1));
    • &arr 是数组指针,&arr + 1 会跳过整个 arr 数组(6个字节),指向紧挨着数组末尾的下一个内存地址
    • strlen 将从这里开始向后寻找 '\0'
    • 结果:由于跳过了整个数组,它从更远的地方开始寻找,最终返回的数值是另一个不确定的、很大的随机值。但是这个随机值又跟前面的随机值有什么关系吗?肯定有的,这个越过整个数组,且整个数组都没有'\0',那么这次随机值,应该比之前小6.
  3. printf("%d\n", strlen(&arr[0] + 1));
    • &arr[0] 是首元素地址,+1 后指向第二个元素 'b' 的地址
    • strlen 将从 'b' 的地址开始,向后寻找 '\0'
    • 结果:它比从 'a' 开始寻找少计了一个字符 'a',但依然会越界。最终返回一个不确定的随机值,不难想到这个值应该比前两行值小1。

图示加强理解:

在这里插入图片描述
在这里插入图片描述

结果展示:

在这里插入图片描述
在这里插入图片描述

通过运行展示,结果完全符合预期!

题目2:字符串的真相

代码语言:javascript
复制
int main()
{
	char arr[] = "abcdef";
	printf("%d\n", strlen(arr));
	printf("%d\n", strlen(arr + 0));
	printf("%d\n", strlen(*arr));
	printf("%d\n", strlen(arr[1]));
	printf("%d\n", strlen(&arr));
	printf("%d\n", strlen(&arr + 1));
	printf("%d\n", strlen(&arr[0] + 1));
	return 0;
}

核心变化分析: 与上一题最根本的不同在于数组的初始化方式:

代码语言:javascript
复制
char arr[] = "abcdef";

这行代码会用字符串字面量 "abcdef" 来初始化数组。编译器会自动在末尾添加一个字符串结束符 '\0'。 因此,数组在内存中的实际布局是:{'a', 'b', 'c', 'd', 'e', 'f', '\0'},它是一个合法的、以 \0 结尾的字符串。 逐行详细解析与运行结果:

  1. printf("%d\n", strlen(arr));
    • arr 是数组名,代表首元素地址(&arr[0]),指向 'a'
    • strlen'a' 开始向后计数,依次经过 'b', 'c', 'd', 'e', 'f',然后在下一个位置遇到了 '\0'
    • 结果6strlen 统计 '\0' 之前的字符个数,共6个。
  2. printf("%d\n", strlen(arr + 0));
    • arr + 0 的值与 arr 完全相同,都是指向 'a' 的地址。因此,它的遍历路径与第一行完全一致。
    • 结果6
  3. printf("%d\n", strlen(*arr));
    • *arr 是解引用操作,得到的是字符 'a',其ASCII码为 97
    • strlen 期望一个地址,但被传入了一个整数值 97
    • 程序将 97 当作内存地址 0x00000061
    • 结果程序崩溃 (Segment Fault),这是一个非法的内存访问。
  4. printf("%d\n", strlen(arr[1]));
    • arr[1] 是数组的第二个元素,即字符 'b',其ASCII码为 98。与第三行同理,strlen 被传入 98,并试图访问地址 0x00000062
    • 结果程序崩溃 (Segment Fault)
  5. printf("%d\n", strlen(&arr));
    • &arr 的类型是 char (*)[7](指向整个数组的指针),但其值与 arrchar*)相同,都指向内存块的起始位置。
    • 当传递给 strlen(参数为 const char*)时,它被转换回字符指针。
    • strlen 依然从 'a' 开始寻找 '\0'
    • 结果6
  6. printf("%d\n", strlen(&arr + 1));
    • &arr 是数组指针,&arr + 1 会跳过整个 arr 数组(7个字节)。
    • 如内存图所示,它指向了数组末尾 '\0' 之后的下一个地址。
    • strlen 从这里开始向后寻找 '\0'。这是一个未初始化的内存区域,'\0' 出现的位置是随机的。
    • 结果一个不确定的随机值
  7. printf("%d\n", strlen(&arr[0] + 1));
    • &arr[0] 是首元素 'a' 的地址。
    • &arr[0] + 1 指向第二个元素 'b' 的地址。
    • strlen'b' 开始计数,依次经过 'c', 'd', 'e', 'f',然后遇到 '\0'
    • 结果5。它统计了从 'b''f' 这5个字符。
在这里插入图片描述
在这里插入图片描述

结果展示:

在这里插入图片描述
在这里插入图片描述

符合预期!下一关GO


第二关:数组名的真正身份

本关简介:数组名在C语言中有着多重身份时而代表整个数组时而退化为指针。这种身份切换是理解数组与指针关系的关键,也是很多高级指针操作的基础。掌握这一关,你就能看清数组名在不同上下文中的真实面目。

题目3:一维数组的身份危机

代码语言:javascript
复制
int main()
{
	int a[] = { 1,2,3,4 };
	printf("%d\n", sizeof(a));
	printf("%d\n", sizeof(a + 0));
	printf("%d\n", sizeof(*a));
	printf("%d\n", sizeof(a + 1));
	printf("%d\n", sizeof(a[1]));
	printf("%d\n", sizeof(&a));
	printf("%d\n", sizeof(*&a));
	printf("%d\n", sizeof(&a + 1));
	printf("%d\n", sizeof(&a[0]));
	printf("%d\n", sizeof(&a[0] + 1));
	return 0;
}

数组名的意义

  1. sizeof(数组名):这里的数组名表示整个数组,计算的是整个数组的大小。
  2. &数组名:这里的数组名表示整个数组,取出的是整个数组的地址。
  3. 除此之外所有的数组名都表示首元素的地址

逐行详细解析:

  1. printf("%d\n", sizeof(a));
    • 分析a 单独放在 sizeof 内,代表整个数组
    • 计算4个元素 × 4字节/元素 = 16字节
    • 结果16
  2. printf("%d\n", sizeof(a + 0));
    • 分析a + 0 不是单独的 a,数组名退化为首元素地址(int*类型)
    • 计算:指针的大小,在32位系统下为 4字节,64位系统下为 8字节
    • 结果4或8(取决于系统位数)
  3. printf("%d\n", sizeof(*a));
    • 分析a 退化为首元素地址,*a 就是对首元素地址解引用,得到第一个元素 a[0]int类型)
    • 计算sizeof(int) = 4字节
    • 结果4
  4. printf("%d\n", sizeof(a + 1));
    • 分析a + 1a 退化为首元素地址,a + 1 指向第二个元素 a[1] 的地址(int*类型)
    • 计算:指针的大小
    • 结果4或8(取决于系统位数)
  5. printf("%d\n", sizeof(a[1]));
    • 分析a[1] 是数组的第二个元素(int类型)
    • 计算sizeof(int) = 4字节
    • 结果4
  6. printf("%d\n", sizeof(&a));
    • 分析&a 取出的是整个数组的地址,类型为 int (*)[4](指向包含4个int的数组的指针)
    • 计算:数组指针的大小,仍然是指针的大小
    • 结果4或8(取决于系统位数)
  7. printf("%d\n", sizeof(*&a));
    • 分析&a 是数组指针,*&a 是对数组指针解引用,得到整个数组
    • 等价于sizeof(a)(第1行的情况)
    • 计算4个元素 × 4字节/元素 = 16字节
    • 结果16
  8. printf("%d\n", sizeof(&a + 1));
    • 分析&a 是数组指针,&a + 1 跳过整个数组,指向数组末尾的下一个位置,类型仍是 int (*)[4]
    • 计算:指针的大小
    • 结果4或8(取决于系统位数)
  9. printf("%d\n", sizeof(&a[0]));
    • 分析&a[0] 取出第一个元素的地址(int*类型)
    • 计算:指针的大小
    • 结果4或8(取决于系统位数)

最终运行结果(x86下)

在这里插入图片描述
在这里插入图片描述

题目4:二维数组的维度跳跃

代码语言:javascript
复制
int main()
{
	int a[3][4] = { 0 };
	printf("%d\n", sizeof(a));
	printf("%d\n", sizeof(a[0][0]));
	printf("%d\n", sizeof(a[0]));
	printf("%d\n", sizeof(a[0] + 1));
	printf("%d\n", sizeof(*(a[0] + 1)));
	printf("%d\n", sizeof(a + 1));
	printf("%d\n", sizeof(*(a + 1)));
	printf("%d\n", sizeof(&a[0] + 1));
	printf("%d\n", sizeof(*(&a[0] + 1)));
	printf("%d\n", sizeof(*a));
	printf("%d\n", sizeof(a[3]));
	return 0;
}

对于二维数组 int a[3][4]

  • 它可以看作是一个包含 3 个元素的数组,每个元素又是一个包含 4 个 int 的一维数组
  • a 的类型是 int [3][4]
  • a[0] 的类型是 int [4](第一行)
  • a[0][0] 的类型是 int

逐行详细解析:

  1. printf("%d\n", sizeof(a));
    • 分析a 单独在 sizeof 中,代表整个二维数组
    • 计算3行 × 4列 × 4字节 = 48字节
    • 结果48
  2. printf("%d\n", sizeof(a[0][0]));
    • 分析a[0][0] 是第一个元素(int类型)
    • 计算sizeof(int) = 4字节
    • 结果4
  3. printf("%d\n", sizeof(a[0]));
    • 分析a[0] 是第一行的数组名,单独在 sizeof 中,代表整个第一行
    • 计算4个int × 4字节 = 16字节
    • 结果16
  4. printf("%d\n", sizeof(a[0] + 1));
    • 分析a[0] 不是单独在 sizeof 中,退化为指向第一行第一个元素的指针(int*),+1 后指向 a[0][1]的地址。
    • 计算:指针的大小
    • 结果4或8(32位/64位系统)
  5. printf("%d\n", sizeof(*(a[0] + 1)));
    • 分析a[0] + 1 是指向 a[0][1] 的指针,解引用得到 a[0][1]int类型)
    • 计算sizeof(int) = 4字节
    • 结果4
  6. printf("%d\n", sizeof(a + 1));
    • 分析:
      • a 不是单独在 sizeof 中,因此从 int [3][4] 类型退化为 int (*)[4] 类型(指向包含4个int的数组的指针)
      • a + 1 进行指针运算:由于 a 是指向一维数组的指针,+1 会跳过整个一维数组的大小(4×4=16字节)
      • 因此 a + 1 指向第二行 a[1] 的地址。
    • 计算:计算的是指针变量本身的大小,而不是指针所指向内容的大小
    • 结果4或8(32位/64位系统)
  7. printf("%d\n", sizeof(*(a + 1)));
    • 分析:通过上面的理解可以得到a + 1 是指向第二行的指针,解引用得到第二行 a[1]int [4]类型)
    • 计算:一行的大小 = 4个int × 4字节 = 16字节
    • 结果16
  8. printf("%d\n", sizeof(&a[0] + 1));
    • 分析&a[0] 取出第一行的地址(int (*)[4]类型),+1 后指向第二行
    • 计算:指针的大小
    • 结果4或8(32位/64位系统)
  9. printf("%d\n", sizeof(*(&a[0] + 1)));
    • 分析
      • &a[0]:取出第一行 a[0] 的地址,类型为 int (*)[4](指向包含4个int的数组的指针)
      • &a[0] + 1:指针运算,跳过第一行(16字节),指向第二行 a[1] 的地址
      • *(&a[0] + 1):对指向第二行的指针解引用,得到第二行 a[1] 本身,类型为 int [4]
    • 计算sizeof(int [4]) = 4 × sizeof(int) = 4 × 4 = 16字节
    • 结果16
  10. printf("%d\n", sizeof(*a));
    • 分析a 退化为指向第一行的指针,解引用得到第一行 a[0]int [4]类型)
    • 计算:一行的大小 = 16字节
    • 结果16
  11. printf("%d\n", sizeof(a[3]));
    • 分析a[3] 在编译时被看作一个 int [4] 类型,sizeof 在编译时计算类型大小,不关心下标是否越界,只关心类型。
    • 计算:一行的大小 = 16字节
    • 结果16

最终运行结果(32位系统下):

在这里插入图片描述
在这里插入图片描述

下一关!冲冲冲~


第三关:指针运算的深水区

本关简介:指针运算不仅仅是地址的加减,更是类型系统的精妙体现。本关的三道题目将带你进入指针的核心地带,理解指针类型如何影响运算结果,以及多级指针如何在不同抽象层次上操作数据。这是区分指针新手和高手的关键分水岭。

题目5:&a + 1 的魔法

代码语言:javascript
复制
int main()
{
	int a[5] = { 1, 2, 3, 4, 5 };
	int* ptr = (int*)(&a + 1);
	printf("%d,%d", *(a + 1), *(ptr - 1));
	return 0;
}

关键点说明(一步步)

  1. int a[5] = {1,2,3,4,5}; 在内存中分配了一个包含 5 个 int 的数组 a,下标 0..4sizeof(int) = 4 字节。
  2. &a 的类型是 int (*)[5] —— 指向整个数组的指针。与之相比,a(在大多数表达式中)会退化为 int *,指向第 0 个元素(&a[0])。
  3. &a + 1:对 &a 做指针加法。因为 &a 的类型是指向 int[5] 的指针,所以 &a + 1 会跳过整个数组的字节数,即地址增加 sizeof(int[5]) = 5 * sizeof(int)。结果是 数组的末尾之后的地址。这个地址是合法用于进行指针运算的(得到 one-past-end),但不可直接解引用该 one-past-end 指针。
  4. (int*)(&a + 1):把上面得到的地址强制转换为 int *。现在该指针指向“数组最后一个元素之后的位置”(即 a + 5)。因此 (int*)(&a + 1)a + 5 是等价的指针值(类型不同但地址相同)。
  5. int* ptr = (int*)(&a + 1); 所以 ptr 指向 a + 5 的位置(逻辑上的 one-past-the-last 元素),不能解引用 ptr 本身,但可以做 ptr - 1 得到最后一个元素的地址。
  6. *(a + 1):即 a[1],值为 2
  7. *(ptr - 1)ptra + 5ptr - 1 就是 a + 4,解引用即得到 a[4],值为 5

因此 printf 会输出 2,5

在这里插入图片描述
在这里插入图片描述

结果展示:

在这里插入图片描述
在这里插入图片描述

题目6:类型不匹配的地址游戏

代码语言:javascript
复制
int main()
{
	int a[5][5];
	int(*p)[4];
	p = a;
	printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
	return 0;
}
在这里插入图片描述
在这里插入图片描述

二维数组在形式上很想我图中靠左位置给出的这样,但实际在c语言内存中是按照顺序存储的,也就是考右边这样。

逐步关键点(与图示对应):

  1. 数组与内存布局(图最左)
    • aint a[5][5],内存上是连续的 25 个 int,按行排列: 行 0(索引 0)到行 4(索引 4),每行有 5 个格子(列 0…4)。
    • 用“格子”标记:base + 0a[0][0]base + 1a[0][1],……,base + 22a[4][2](以 int 单位计)。
  2. 指针 p 的类型与含义(图中红色 p)
    • int (*p)[4]; 表示 p 指向「长度为 4 的 int 数组」。
    • p = a;a 的起始地址赋给了 p(地址相同,即 pabase 都是 &a[0][0])。赋值只是复制地址,不改变内存布局。
  3. p 对“行”的解释(图中每个彩色块代表 p 的一行)
    • 由于 p 类型是 (*p)[4],指针算术 p + k 会按 sizeof(int[4]) = 4 * sizeof(int) 字节移动。
    • 因此在“以 int 为单位”的视角下,p + 0base + 0(灰色块), p + 1base + 4(黄色块起始), p + 2base + 8(紫色块起始), p + 3base + 12(黄色/另色块起始), p + 4base + 16(图中亮色块起始)。
    • 这些在图上正是红色箭头 p+0, p+1, ... p+4 指的位置。
  4. 为什么图上会看到“每个 p 行只有 0…3 的编号”
    • 因为 p 认为每行只有 4 列(索引 0…3),所以在每个 p+k 彩色块内部标了 0 1 2 3。那是 p 按类型解释出来的列编号,不代表内存真实缺少第五列——内存真实仍有 5 个 int行(由 a 决定)。
  5. a 的真实行跨距(stride)是 5 个 int
    • a 本来每行是 5 个 int,所以 a + 1 将移动 5 个 int 单位(而不是 p+1 的 4 个 int)。这就是 a 的行间隔比 p 的行间隔大 1 个 int
  6. 计算 &p[4][2] 的元素偏移(以 int 为单位)
    • p+4 移动 4 * (4 ints) = 16int(因为每 p 行是 4 个 int)。
    • 再加 [2] → 再加 2 个 int
    • 因此 &p[4][2] 的偏移 = 16 + 2 = 18(即 base + 18,以 int 单位)。
    • 字节偏移 = 18 * 4 = 72 字节。(对应图中 p+4 起点向右数后第 2 个格子)
  7. 计算 &a[4][2] 的元素偏移(以 int 为单位)
    • a[4][2] 的偏移按 a 的列数 5 计算:行偏移 4 * 5 = 20,再加列 + 220 + 2 = 22
    • 因此 &a[4][2] 的偏移 = 22(即 base + 22)。
    • 字节偏移 = 22 * 4 = 88 字节。(对应图中靠右的绿色标记格)
  8. 两者相减(元素数量差)
    • &p[4][2] - &a[4][2](以 int 为单位) = 18 - 22 = -4
    • 换成字节:72 - 88 = -16 字节 → 除以 sizeof(int)=4 得到 -4
    • 这正是你在 VS 中看到的数值 -4。(图中可以直观看出 p 的第 4 行在物理内存里比 a 的第 4 行要“左移” 4 个格子)
  9. 为什么不是 0(对比常见误解)
    • 关键误解点:p = a 只是地址一致,但 p 在做指针算术时使用的是其声明的行长度(4)a 在做索引时使用的是真实行长度(5)。两种计算使用了不同的 stride,所以索引到的格子不同,地址不相同。画的彩色条块正清楚显示了这种“错位”。
  10. int类型怎么打印为地址?
    • 这时传入一个int类型数据(有符号数),编译器会将这个数字在内存中存储的值认为是地址。也就是-4的补码被认为成地址
    • -4 的原、反、补码,及内存中存储的值。

故结果为(x86):

在这里插入图片描述
在这里插入图片描述

题目7:三级指针的终极挑战

代码语言:javascript
复制
int main()
{
	char* c[] = { "ENTER","NEW","POINT","FIRST" };
	char** cp[] = { c + 3,c + 2,c + 1,c };
	char*** cpp = cp;
	printf("%s\n", **++cpp);
	printf("%s\n", *-- * ++cpp + 3);
	printf("%s\n", *cpp[-2] + 3);
	printf("%s\n", cpp[-1][-1] + 1);
	return 0;
}

这道应该是最复杂的了,打眼一看,三级指针,那么就数形结合,先抽象出他们在内存中的存储和指针指向(画成竖向的完全是为了方便看,内存中还是连续存储的)。根据代码可以画出如下图形(自己能够看懂就行):

在这里插入图片描述
在这里插入图片描述
  1. printf("%s\n", **++cpp);

++cpp 是整个表达式求值的起点,它执行了一个关键的前置自增操作。在操作执行前,三级指针 cpp 正指向二级指针数组 cp 的起始位置,即 cp[0] 的地址,而 cp[0] 中存储的值是 c + 3(即指向指针数组 c 中第四个元素 c[3] 的地址)。当 ++cpp 执行时,cpp 会根据其类型 char ***指向元素的大小进行一次内存地址的跨越,从而使其从原来指向 &cp[0] 更新为指向 &cp[1]。这个操作本身并不改变 cp 数组或 c 数组中的任何数据,它仅仅移动了观察者 cpp 的“视线”,为后续的解引用操作准备好了新的操作对象。这是整个链条中的第一个物理地址偏移,至关重要。

这种自增自减的表达式常常也被叫做带有副作用的表达式,因为他会改变指针指向。

在操作完成后,第一次解引用 *cpp为取出 cp[1] 中存储的值,得到 c + 2(这是指向 c[2] 的指针)。第二次解引用 **cpp,即对 c + 2 进行解引用,即 *(c + 2)拿到 c[2] 中存储的值,即字符串 "POINT" 的地址。最后打印字符串"POINT"

在这里插入图片描述
在这里插入图片描述

第一行代码执行完毕后,内存中的指针有了新指向,也就是上面这个图片所示,接下来进行第二个操作。 2. printf("%s\n", *-- * ++cpp + 3); 这里呢,最好是先理清运算顺序,可以参考我的这篇博客里的运算符优先级表格,看完之后就可以知道先是自增自减,然后解引用,最后加法。也就是:

代码语言:javascript
复制
// *-- * ++cpp + 3
1.++cpp      
2.*++cpp    
3.--*++cpp   
4.*--*++cpp  
5.+ 3        

首先进行++cpp操作, 之前cpp 指向 cp[1](存储着 c + 2),操作后cpp 现在指向 cp[2](存储着 c + 1)。然后进行一次解引用 *cpp,取出 cp[2] 中存储的值,得到 c + 1(指向 c[1]),接着前缀自减 -- * ++cpp,即对 c + 1 进行自减操作,这实际上修改了 cp[2] 中存储的值!结果就是c + 1 变为 c + 0(现在 cp[2] 存储着 c + 0)。接着第二次解引用 *-- * ++cpp,对 c + 0 进行解引用,即 *(c + 0),得到 c[0] 中存储的值,即字符串 "ENTER" 的地址,最后进行指针加法 + 3,即字符串指针从首字符向后移动3个字符位置,得到字符指针指向字符 'E' 后面的位置,即从 'E' 开始``,最后从 "ENTER" + 3 开始输出,直到遇到 '\0'

在这里插入图片描述
在这里插入图片描述

此时指针又有新的改变,即如上图所示。 3. printf("%s\n", *cpp[-2] + 3);

第三个 printf 语句的执行始于通过 cpp[-2] 进行地址回溯,该操作等价于 *(cpp - 2),由于此时 cpp 正指向 cp[2],向前回退两个 char** 元素后便定位到了 cp[0] 的地址,并从中取得了存储的值 c + 3(即指向 c[3] 的指针);紧接着通过 *cpp[-2] 对该指针进行解引用,成功获取了 c[3] 所指向的字符串常量 "FIRST" 的首地址;随后,通过 + 3 进行指针算术运算,使该字符串指针从首字符 'F' 向后移动三个字符的位置,最终指向了字符 'S'printf 函数便从这个新位置开始输出字符,直到遇见字符串结束符 \0,因此在屏幕上打印出了子串 "ST"。 4. printf("%s\n", cpp[-1][-1] + 1);

第四个 printf 语句的执行首先通过 cpp[-1] 进行寻址,该操作等价于 *(cpp - 1),由于 cpp 当前指向 cp[2],向前回退一个 char** 元素后便定位到 cp[1] 的地址,并从中取得了未被修改过的值 c + 2;接着通过第二层下标运算 cpp[-1][-1](等价于 *(cpp[-1] - 1))对该指针进行递减,计算 (c + 2) - 1 得到 c + 1,再解引用获得了 c[1] 所指向的字符串 "NEW" 的首地址;随后,通过 + 1 使字符串指针从首字符 'N' 向后移动一个字符位置,指向了字符 'E'printf 函数便从这个位置开始输出字符,直到遇见字符串结束符 \0,因此在屏幕上打印出了子串 "EW"结果展示(x86):

在这里插入图片描述
在这里插入图片描述
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-11-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 第一关:sizeof vs strlen 基础对决
    • 题目1:字符数组的陷阱
    • 题目2:字符串的真相
  • 第二关:数组名的真正身份
    • 题目3:一维数组的身份危机
    • 题目4:二维数组的维度跳跃
  • 第三关:指针运算的深水区
    • 题目5:&a + 1 的魔法
    • 题目6:类型不匹配的地址游戏
    • 题目7:三级指针的终极挑战
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档