【指针三:穿越编程边界的超能力】(下)

简介: 【指针三:穿越编程边界的超能力】

【指针三:穿越编程边界的超能力】(上):https://developer.aliyun.com/article/1424748


数组名就是数组首元素的地址


   两个特殊情况:


       1.sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节

       2.&数组名,这里的数组名表示整个数组,取出的是整个数组的地址


"abcdef" - 常量字符串 - [a,b,c,d,e,f,\0]

char* p = "abcdef";//这里指针变量p存放的是'a'的地址

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

p是一个指针变量,4/8

   printf("%d\n", sizeof(p + 1));

p+1是第二个字符'b'的地址,地址就是指针,4/8

   printf("%d\n", sizeof(*p));

*p就是字符'a',字符'a'的数据类型是char,sizeof(char), 1

   printf("%d\n", sizeof(p[0]));

p[0]就是字符'a',字符'a'的数据类型是charsizeof(char), 1

   printf("%d\n", sizeof(&p));

p是指针变量,&p就是二级指针,同样是地址,4/8

   printf("%d\n", sizeof(&p + 1));

p是指针变量,&p就是二级指针,&p+1就是跳过这个二级指针变量,同样是地址,4/8

   printf("%d\n", sizeof(&p[0] + 1));

&p[0]就是取字符'a'的地址,&p[0]+1就是第二个字符'b'的地址,地址就是指针,4/8

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

常量字符串中有\0,字符串长度是\0之前的字符,不包括\0,6

   printf("%d\n", strlen(p + 1));

p+1就是字符'b'的地址, 从常量字符串的第二个元素的位置寻找\0,5

   printf("%d\n", strlen(*p));

*p就是字符'a'-对应的ASCII码值为97,把97当作地址,非法访问,error

   printf("%d\n", strlen(p[0]));

p[0]就是字符'a'-对应的ASCII码值为97,把97当作地址,非法访问,error

   printf("%d\n", strlen(&p));

&p是二级指针,存放的是指针变量p的地址,不知道\0的位置,随机值

   printf("%d\n", strlen(&p + 1));

&p+1是跳过这个二级指针变量,不知道\0的位置,随机值

   printf("%d\n", strlen(&p[0] + 1))

&p[0]+1就是第二个字符'b'的地址,从常量字符串的第二个元素的位置寻找\0,5


代码图解:



运行结果:



3、二维数组的sizeof


#include<stdio.h>
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;
}


printf("%d\n", sizeof(a));

数组名单独放在sizeof内部,a表示整个数组,计算整个数组的大小,12*sizeof(int)=48

printf("%d\n", sizeof(a[0][0]));

a[0][0]表示第一行第一列的数据,数据类型是整型,4

printf("%d\n", sizeof(a[0]));

表示二维数组的第一行元素,该行有4个元素,可以当作一个一维数组存放四个数据,a[0]是第一行这个一维数组的数组名,数组名单独出现,计算的是整个数组的大小,16

printf("%d\n", sizeof(a[0] + 1));

a[0]是第一行这个一维数组的数组名,数组名不单独出现,a[0]表示第一行数组的首元素地址,也就是a[0][0]的地址,所以a[0]+1是第一行第二个元素的地址,4/8

printf("%d\n", sizeof(*(a[0] + 1)));

a[0]+1是第一行第二个元素的地址,解引用就是a[0][1],数据类型是整型,4

printf("%d\n", sizeof(a + 1));

a是这个二维数组的数组名,数组名不单独出现,a表示这个二维数组的首元素地址,a+1就表示第二行这个一维数组的地址,4/8

printf("%d\n", sizeof(*(a + 1)));

*(a + 1)) <==> a[1],表示二维数组的第二行元素,该行有4个元素,可以当作一个一维数组存放四个数据,a[1]是第二行这个一维数组的数组名,数组名单独出现,计算的是整个数组的大小,16

printf("%d\n", sizeof(&a[0] + 1));

&a[0]表示取出第一行一维数组的地址,&a[0]+1就表示第二行这个一维数组的地址,4/8

printf("%d\n", sizeof(*(&a[0] + 1)));

*(&a[0] + 1) <==> *(a+1) <==> a[1],16

printf("%d\n", sizeof(*a));

数组名不单独出现,a表示首元素地址,也就是第一行的地址,*a <==> *(a+0) <==> a[0],16

printf("%d\n", sizeof(a[3]));

越界,但是sizeof并不会访问内存,a[3] <==> a[2] <==> a[1] <==> a[0],1


代码图解:



运行结果:



上面的sizeof(a[3])越界了,但是程序依然没报错,依然可以运行,为什么呢?



一个程序需要经过编译+链接->可执行程序->运行,sizeof不访问内存,在编译期间就已经执行了,而b = a + 1是在运行期间执行的。


总结: 数组名的意义:


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


4、指针笔试题


demo1:


#include<stdio.h>
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 }; 定义了一个整数数组 a,并初始化其元素为 1、2、3、4 和 5。
  2. int* ptr = (int*)(&a + 1); 定义了一个指针 ptr,它指向数组 a 的后一个位置。在这里,&a 获取整个数组的地址,然后 +1 使指针指向下一个内存位置,即数组末尾后面的位置。
  3. printf("%d,%d", *(a + 1), *(ptr - 1)); a是数组名,数组名不单独出现,a表示数组首元素的地址,a+1表示第二个元素的地址,*(a + 1)表示第二个元素,prt是int* 类型,此时表示跳过整个数组的地址,ptr - 1就指向了数组的第五个元素的地址,*(ptr - 1)就是5
  4. 程序的结果是什么?2,5


demo2:


#include<stdio.h>
//由于还没学习结构体,这里告知结构体的大小是20个字节
struct Test
{
  int Num;
  char* pcName;
  short sDate;
  char cha[2];
  short sBa[4];
}*p = (struct Test*)0x100000;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
  //指针的类型决定了指针+-操作的步长
  printf("%p\n", p + 0x1);
  printf("%p\n", (unsigned long)p + 0x1);
  printf("%p\n", (unsigned int*)p + 0x1);
  return 0;
  //程序的结果是什么?
}


  1. printf("%p\n", p + 0x1); 这里对指针 p 进行加法操作,p + 0x1 实际上是将指针 p 往后移动了一个结构体 Test 的大小(20个字节),所以结果为 0x100000 + 0x14 = 0x100014。
  2. printf("%p\n", (unsigned long)p + 0x1); 这里强制将指针 p 转换为无符号长整型后再进行加法操作,即对数值进行计算,0x100000 + 0x1 = 0x100001。
  3. printf("%p\n", (unsigned int*)p + 0x1); 这里强制将指针 p 转换为整型指针后再进行加法操作,整型指针的大小是4个字节,所以在进行加法操作时会跳过4个字节,即 0x100000 + 0x4 = 0x100004。
  4. 程序的结果是什么?0x10014,0x10001,0x10004


demo3:


#include<stdio.h>
int main()
{
  int a[4] = { 1, 2, 3, 4 };
  int* ptr1 = (int*)(&a + 1);
  int* ptr2 = (int*)((int)a + 1);
  printf("%x,%x", ptr1[-1], *ptr2);
  return 0;
  //程序的结果是什么?
}


代码图解:



  1. int* ptr1 = (int*)(&a + 1); 定义了一个指针 ptr1,它指向数组 a 后一个位置。在这里,&a 获取整个数组的地址,然后 +1 使指针指向下一个整数数组,即在数组 a 之后的位置。
  2. int* ptr2 = (int*)((int)a + 1); 这一行定义了一个指针 ptr2,它指向数组 a 的首地址后一个字节的位置。在这里,(int)a 将数组 a 的首地址转换为整数类型,然后 +1 使指针指向首地址后一个字节的位置。


让我们来计算这两个值的具体结果:


  • ptr1[-1]: ptr1 指向了数组 a 后面的位置,而 ptr1[-1] <==> *(ptr1-1),也就是从数组的后面的位置减1,由于ptr1的类型是指针类型,-1操作的步长是所指向数据类型,也就是sizeof(int)4个字节,指向元素3的位置。


*ptr2: ptr2 指向数组 a 的首地址后一个字节的位置,所以 *ptr2 取出了元素1的00 00 00三个字节,取出来元素2的02字节,00 00 00 02,由于是小端存储,实际数据是0x02 00 00 00.


程序的结果是什么?4,2000000


demo4:


#include<stdio.h>
int main()
{
  int a[3][2] = { (0, 1), (2, 3), (4, 5) };
  int* p;
  p = a[0];
  printf("%d", p[0]);
  return 0;
  //程序的结果是什么?
}


  1. int a[3][2] = { (0, 1), (2, 3), (4, 5) }; 这一行定义了一个二维整数数组 a,其中有 3 行 2 列,每个元素被初始化为 (0, 1)、(2, 3) 和 (4, 5)。请注意,(0, 1)、(2, 3) 和 (4, 5) 都是逗号运算符的结果,实际上只有后面的值 (1, 3, 5) 会被用于初始化。
  2. int* p; 这一行定义了一个整型指针 p。
  3. p = a[0]; 这里将指针 p 指向二维数组 a 的第一行的首地址,即数组 a[0] 的首地址。
  4. printf("%d", p[0]); 这一行使用 printf 函数打印 p[0] 的值。由于 p 指向了数组 a[0] 的首地址,所以 p[0] 相当于访问了数组 a[0] 的第一个元素,即 (1, 3, 5) 中的第一个元素,也就是 1
  5. 程序的结果是什么?1


demo5:


#include<stdio.h>
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;
  //程序的结果是什么?
}


代码图解



 

  1. int a[5][5]; 定义了一个二维整数数组 a,有 5 行 5 列。由于没有初始化,数组 a 的元素值是未定义的。
  2. int(*p)[4];定义了一个指向包含 4 个整数的一维数组的指针 p
  3. p = a; 这里将指针 p 指向二维数组 a 的第一行的首地址,即数组 a[0] 的首地址。
  4. &p[4][2] - &a[4][2] :这一表达式用来计算 &p[4][2] 与 &a[4][2] 之间的距离,也就是这两个地址之间相差的元素个数。
  5. &p[4][2] - &a[4][2]: &p[4][2] 为指针 p 所指向的位置的第 4 行第 2 列的元素地址,而 &a[4][2] 为数组 a 的第 4 行第 2 列的元素地址。这两个地址之间相差 4 个 int 类型的元素,因为 p 指向的一维数组有 4 个元素。
  6. 程序的结果是什么?FFFFFFFC,-4


demo6:


#include<stdio.h>
int main()
{
  int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
  int* ptr1 = (int*)(&aa + 1);
  int* ptr2 = (int*)(*(aa + 1));
  printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));
  return 0;
  //程序的结果是什么?
}


  1. int* ptr1 = (int*)(&aa + 1); 定义了一个指针 ptr1,它指向数组 aa 后一个位置。在这里,&aa 获取整个数组 aa 的地址,然后 +1 使指针指向下一个二维整数数组的位置,即在数组 aa 之后的位置。
  2. int* ptr2 = (int*)(*(aa + 1)); 定义了一个指针 ptr2,它指向数组 aa 的第二行的首地址。*(aa + 1) 实际上是取数组 aa 的第二行的首元素的地址。
  3. 现在,让我们计算这两个值的具体结果:
  4. *(ptr1 - 1): ptr1 指向了数组 aa 后面的位置,而 ptr1 - 1 则是指向数组 aa 之前一个整数数组的首元素。这个整数数组是 {6, 7, 8, 9, 10},所以 *(ptr1 - 1) 取出了这个整数数组的最后一个元素,即 10。
  5. *(ptr2 - 1): ptr2 指向数组 aa 的第二行的首地址,而 ptr2 - 1 则是指向数组 aa 第二行之前一个整数的位置。这个整数是 5,所以 *(ptr2 - 1) 取出了 5。
  6. 程序的结果是什么?10,5


demo7:


#include <stdio.h>
int main()
{
  char* a[] = { "work","at","mihayou" };
  char** pa = a;
  pa++;
  printf("%s\n", *pa);
  return 0;
  //程序的结果是什么?
}


  1. char* a[] = { "work","at","mihayou" }; 这一行定义了一个字符指针数组 a,其中包含三个元素,分别指向字符串常量 "work"、"at" 和 "mihayou"。
  2. char** pa = a; 这一行定义了一个指向字符指针的指针 pa,并将其指向字符指针数组 a 的第一个元素,即 "work" 的地址。
  3. pa++; 这里将指针 pa 向后移动一个位置,现在 pa 指向字符指针数组 a 的第二个元素,即 "at" 的地址。
  4. printf("%s\n", *pa); 这一行使用 printf 函数打印指针 pa 指向的位置的值,也就是字符指针 "at" 所指向的字符串。


demo8:


#include<stdio.h>
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. char* c[] = { "ENTER","NEW","POINT","FIRST" }; 定义了一个字符指针数组 c,其中包含四个元素,分别指向字符串常量 "ENTER"、"NEW"、"POINT" 和 "FIRST"。
  2. char** cp[] = { c + 3,c + 2,c + 1,c }; 定义了一个字符指针指针数组 cp,其中包含四个元素,每个元素是一个字符指针,并分别指向 c 数组的不同位置。具体地说,cp[0] 指向 c 数组的第四个元素 "FIRST" 的地址,cp[1] 指向 c 数组的第三个元素 "POINT" 的地址,cp[2] 指向 c 数组的第二个元素 "NEW" 的地址,cp[3] 指向 c 数组的第一个元素 "ENTER" 的地址。
  3. char*** cpp = cp; 定义了一个字符指针指针指针 cpp,并将其指向字符指针指针数组 cp 的第一个元素,即 cp[0],也就是指向 "FIRST" 的地址。
  4. printf("%s\n", **++cpp); 这一行先执行 ++cpp,将 cpp 指向 cp 数组的第二个元素,即 cp[1],然后再执行 **++cpp,取出指针 cpp 指向的位置的值,也就是字符指针 "POINT" 所指向的字符串。所以这一行输出 "POINT"。
  5. printf("%s\n", *-- * ++cpp + 3); 先执行 ++cpp,将 cpp 指向 cp 数组的第三个元素,即 cp[2],然后再执行 *++cpp,取出指针 cpp 指向的位置的值,接着执行 -- * ++cpp,先对指针 cpp 指向的位置进行解引用,得到 "c+1",然后再执行 -- 操作,指针 cp 指向 cp 数组的第一个元素,即 c[0]。接下来,执行 * c[0] + 3,取出 "ENTER" 的地址并加上 3,所以这一行输出 "ER".
  6. printf("%s\n", *cpp[-2] + 3); 访问 cpp[-2] <==> **(cpp-2),即 cp 数组的第一个元素,指向 "c+3" 的地址。然后执行 * cpp[-2] + 3,取出 "FIRST" 的地址并加上 3,所以这一行输出 "ST".
  7. printf("%s\n", cpp[-1][-1] + 1); 访问 cpp[-1],即 cp 数组的第一个元素,指向 "c+2" 的地址。然后再访问 cpp[-1][-1] <==> *(*(cpp-1)-1),即指向 "c+2" 的指针减去 1,指向 "NEW" 的地址。最后执行 cpp[-1][-1] + 1,取出 "NEW" 的地址并加上 1,所以这一行输出 "EW".
  8. 程序的结果是什么?POINT,ER,ST,EW


相关文章
|
2月前
|
C++
【编码狂想】指针航行,链表魔法,解锁结构体和类的编程幻境
【编码狂想】指针航行,链表魔法,解锁结构体和类的编程幻境
58 1
|
1月前
|
存储 编译器 程序员
【C/C++ this指针 20240105更新】探索C++编程之旅:深入理解this指针的魅力与应用
【C/C++ this指针 20240105更新】探索C++编程之旅:深入理解this指针的魅力与应用
28 0
|
3月前
【指针三:穿越编程边界的超能力】(上)
【指针三:穿越编程边界的超能力】
|
3月前
|
存储
【指针二:穿越编程边界的超能力】
【指针二:穿越编程边界的超能力】
|
3月前
|
存储 编译器 C语言
【指针一:穿越编程边界的超能力】
【指针一:穿越编程边界的超能力】
|
3月前
|
存储 C语言
深入浅出 C 语言:学变量、掌控流程、玩指针,全方位掌握 C 编程技能
C 语言介绍 C 语言的特性 C 语言相对于其他语言的优势 C 程序的编译 C 中的 Hello World 程序
48 2
|
4月前
链表中涉及“快慢指针”的编程题—“返回中间节点”
链表中涉及“快慢指针”的编程题—“返回中间节点”
32 0
|
9月前
|
Serverless C语言
头歌C语言实训项目-数组、指针和函数综合编程练习
头歌C语言实训项目-数组、指针和函数综合编程练习
183 0
|
11月前
|
存储 程序员 C语言
C语言编程—指针
学习 C 语言的指针既简单又有趣。通过指针,可以简化一些 C 编程任务的执行,还有一些任务,如动态内存分配,没有指针是无法执行的。所以,想要成为一名优秀的 C 程序员,学习指针是很有必要的。 正如您所知道的,每一个变量都有一个内存位置,每一个内存位置都定义了可使用 & 运算符访问的地址,它表示了在内存中的一个地址。 请看下面的实例,它将输出定义的变量地址:
177 0
|
18天前
|
存储 C语言
C语言 — 指针进阶篇(下)
C语言 — 指针进阶篇(下)
20 0