C语言-八道笔试题由浅入深玩转指针

本文涉及的产品
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
简介: 本文章将带你刷8道比较有意思的指针笔试题,笔者将由深入浅出解析这些题目!必要的题目,作者已经加上内存布局图!希望本文对你有所帮助!

20210913223827553.png一.笔试题1 -指针与一维数组的关系-值

int main()
{
    int a[5] = { 1, 2, 3, 4, 5 };
    int *ptr = (int *)(&a + 1); //&a:取出整个数组的地址,&a+1:跳过一个数组
    //&a的类型为:数组指针  int(*)[5]      所以要强转
    //a为数组名,首元素地址,即为1的地址,+1,跳过一个元素,即为2的地址
    printf( "%d,%d", *(a + 1), *(ptr - 1));  // 2  5
    return 0;
}

20210913220920908.png

二.笔试题2-指针与结构体

//这里告知结构体的大小是20个字节
struct Test
{
    int Num;
    char *pcName;
    short sDate;
    char cha[2];
    short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
int main()
{
    p = 0x00100000;
    //0x1-->对应的值就是1  相当于0x00000001
    printf("%p\n", p + 0x1);//p为结构体指针,指向一个结构体,+1,跳过一个结构体,即跳过20个字节,
    //   0x00100000+20 -> 0x00100020 错误,  要将20转化为16进制再加,或者将16进制0x00100000转化为10进制之后加上20,然后再转化为16进制
    // 20-> 0X00000014 
    //所以最终结果为:0x00100014
    printf("%p\n", (unsigned long)p + 0x1);//将p转化为长整形,+1,即为整形+1,  例如:500+1= 501,
    //所以结果为: 0x00100001
    printf("%p\n", (unsigned int*)p + 0x1);
    //将p强转为无符号整形,+1跳过一个整形->跳过4个字节
    //所以结果为:0x00100004
    return 0;
}

注意坑点:整数+1 ->跳过一个字节  执行普通的加减运算

      而整形指针+1 ->跳过四个字节

指针+1 的步长取决于指针指向的数据的类型


三.笔试题3-指针与一维数组的关系-址

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);  // 4 2000000
    return 0;
}

解析:

int *ptr1 = (int *)(&a + 1):  取出数组的地址+1,跳过一个数组,因为&a的类型为数组指针:int(*)[4] 类型不匹配,所以强转为int类型

prt1[-1]==> *(ptr1+(-1))==>*(ptr1-1)


int *ptr2 = (int *)((int)a + 1)

此时的a代表的首元素地址,地址值是一个常量,

整数+1:相当于跳过一个字节

所以应该画出数组的整体字节布局,注意要考虑小端存放读取时倒着读取的问题


20210913221439852.png


ptr2是整形指针,解引用向后访问4个字节

所以ptr2指向的是00 00 00 02这四个字节

要倒着拿:所以打印结果为:02000000

图解:

20210913221237918.png


注意点:整形+1:加一个字节    如:500+1 = 501  

            整形指针+1:跳过(加)4个字节


四.笔试题4-指针与逗号表达式


int main()
{
   int a[3][2] = { (0, 1), (2, 3), (4, 5) };
   int *p;
   p = a[0];
   printf("%d ",p[0]);  //1
   return 0;
}


坑点:逗号表达式-结果为最后一个表达式的结果

图解:


2021091322205932.png

所以相当于只初始化了前三个元素,后面的元素未初始化,默认为0  


解析:

a[0] : 二维数组第一行的数组名,在这里是首元素地址,即第一行第一个元素的地址

p[0] ==>*(p+0)==>*p

p是整形指针,解引用向后访问4个字节


20210913222449401.png


五.笔试题5-指针与二维数组

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;
}

意:p是数组指针,指向的数组有4个元素

指针-指针得到的是二者之间的元素个数

p[4] = *(p+4)

p[4][2] ==> *(*(p+4)+2)

图解:

20210913222622589.png


&p[4][2]为小地址,&a[4][2]为大地址,小地址减大地址,所以最后结果为-4  


-4:

原码:10000000 00000000 00000000 00000100

反码:11111111 11111111 11111111 11111011

补码:11111111 11111111 11111111 11111100

使用%p方式打印:打印的是-4对应的补码 1111 1111 1111 1111 1111 1111 1111 1100 ->结果为:FFFFFFFC


整数在内存中以补码方式存储,打印地址和打印无符号整数一样,都是打印内存中补码

使用%d方式打印:打印二进制补码对应的原码,=>  -4

注意a是二维数组,对应数组指针的类型为:int(*)[5],指向的是有5个元素的一维数组

而p是数组指针,指向的数组只有4个元素,所以会有警告

->可以写成 int(*p)[4] = (int(*)[4])a 消除警告


六.笔试题6-指针与二维数组

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));    // 5 10
  return 0;
}

解析:

&aa:取出二维数组的地址 &aa+1:跳过二维数组

&二维数组应该使用数组指针接收,现在保存到整形指针,所以要强转。

aa:没有单独放在sizeof内部,没有&数组名,所以代表的是二维数组首元素地址,即二维数组第一行的地址

aa+1:跳过一行

*(aa+1) : 相当于拿到了第二行的数组名 等价于 aa[1]

20210913223015739.png

七.笔试题7-指针与字符指针数组

#include <stdio.h>
int main()
{
    char *a[] = {"work","at","alibaba"};
    char**pa = a;
    pa++;
    printf("%s\n", *pa);
  return 0;
}

解析:

a是数组,元素类型为:char* ,存放的是指向字符串的首字符地址

根据后面初始化的内容确定数组的大小

char** pa = a ; 此处的a是首元素地址,char**类型

--------

char**pa :一颗*说明pa是指针,另一颗*说明pa指向的类型是char*

pa+1:跳过char*

所以从字符a的地址向后打印,遇到\0即停止打印

打印结果为:at

20210913223532721.png

八.笔试题9-指针与字符指针数组(难)

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;
}

这道题比较难,所以我们分表达式解决!


最最最初的内存布局:

20210913223638246.png


解析:**++cpp

前置++,cpp先自增,

20210913223712717.png

此时cpp

(存放了)指向存放c+2地址的空间(地址)

*cpp->得到c+2的地址

**cpp得到c+2中存放的内容->首字符P的地址


所以从首字符P开始向后打印,打印结果为:POINT

解析:*--*++cpp+3

注意:上面的表达式,cpp发生自增,指向的已经是存放c+2地址的空间

++cpp :前置++,cpp发生自增原来是指向存放c+2地址的空间,变为指向存放c+1地址的空间

20210913223712717.png

*++cpp:拿到存放c+1地址的空间,里面存放的是c+1的地址


--*++cpp;前置--,相当于自减解引用cpp之后的内容,即自减c+1的地址,(地址值是常量)。即把原来空间存放的是c+1的地址变成了存放的是c的地址,即现在拿到的是存放c地址的空间


20210913224046864.png


*--*++cpp : 得到c空间中存放的内容(首字符E的地址)


*--*++cpp+3 :从首字符E的地址向后+3 ,即为E的地址


从E的地址向后打印->打印结果为ER

解析:*cpp[-2]+3

20210913223921393.png


cpp[-2] ==>*(cpp+(-2) )==> *(cpp-2)

*cpp[-2]+3 ==> **(cpp-2) )+3

cpp-2:从指向存放c地址空间又变为了指向存放c+3地址的空间

20210913223945363.png

*(cpp-2):得到cpp现在指向的内容,即c+3的地址

**(cpp-2):得到c+3空间的内容(首字符F的地址)

**(cpp-2)+3 :从首字符F的地址向后+3,即为S的地址


从S的地址向后打印,打印结果为:ST

解析:cpp[-1][-1]+1


cpp[-1] ==>*(cpp-1)


cpp[-1][-1] ==> *(*(cpp-1)-1)


cpp[-1][-1] +1 ==> *(*(cpp-1)-1) +1


注意:此时的cpp指向为第二条表达式之后的状态,上面的第三条表达式并没有改变cpp(即cpp没有自增自减以及被赋值),也没有改变其指向内容,所以第四条表达式的初始状态为计算第二条表达式之后的内存布局


20210913224046864.png

cpp-1:变为指向存放c+2地址的空间

20210913224057840.png

*(cpp-1):得到c+2的地址

*(cpp-1)-1 :c+2的地址值(常量)自减,变成了c+1的地址,即得到了c+1的地址

20210913224117751.png

(*(cpp-1)-1): 得到c+1的内容(首字符N的地址)

*(*(cpp-1)-1)+1:得到首字符N向后+1跳过一个字符,即为字符E的地址


从字符E向后打印,打印结果为EW

总结:

能坚持看完,你已经比别人优秀很多了!也感谢你看到最后,如果笔者哪里写错了的话,欢迎大佬们评论区指正!如果此文对你有帮助的话,欢迎留个关注,留个赞再走呀!

相关文章
|
1月前
|
存储 C语言
【C语言篇】深入理解指针3(附转移表源码)
【C语言篇】深入理解指针3(附转移表源码)
35 1
|
16天前
|
C语言
【c语言】指针就该这么学(1)
本文详细介绍了C语言中的指针概念及其基本操作。首先通过生活中的例子解释了指针的概念,即内存地址。接着,文章逐步讲解了指针变量的定义、取地址操作符`&`、解引用操作符`*`、指针变量的大小以及不同类型的指针变量的意义。此外,还介绍了`const`修饰符在指针中的应用,指针的运算(包括指针加减整数、指针相减和指针的大小比较),以及野指针的概念和如何规避野指针。最后,通过具体的代码示例帮助读者更好地理解和掌握指针的使用方法。
41 0
|
14天前
|
C语言
【c语言】指针就该这么学(3)
本文介绍了C语言中的函数指针、typedef关键字及函数指针数组的概念与应用。首先讲解了函数指针的创建与使用,接着通过typedef简化复杂类型定义,最后探讨了函数指针数组及其在转移表中的应用,通过实例展示了如何利用这些特性实现更简洁高效的代码。
11 2
|
15天前
|
C语言
如何避免 C 语言中的野指针问题?
在C语言中,野指针是指向未知内存地址的指针,可能引发程序崩溃或数据损坏。避免野指针的方法包括:初始化指针为NULL、使用完毕后将指针置为NULL、检查指针是否为空以及合理管理动态分配的内存。
|
15天前
|
C语言
C语言:哪些情况下会出现野指针
C语言中,野指针是指指向未知地址的指针,通常由以下情况产生:1) 指针被声明但未初始化;2) 指针指向的内存已被释放或重新分配;3) 指针指向局部变量,而该变量已超出作用域。使用野指针可能导致程序崩溃或不可预测的行为。
|
21天前
|
存储 C语言
C语言32位或64位平台下指针的大小
在32位平台上,C语言中指针的大小通常为4字节;而在64位平台上,指针的大小通常为8字节。这反映了不同平台对内存地址空间的不同处理方式。
|
21天前
|
存储 算法 C语言
C语言:什么是指针数组,它有什么用
指针数组是C语言中一种特殊的数据结构,每个元素都是一个指针。它用于存储多个内存地址,方便对多个变量或数组进行操作,常用于字符串处理、动态内存分配等场景。
|
21天前
|
存储 C语言
C语言指针与指针变量的区别指针
指针是C语言中的重要概念,用于存储内存地址。指针变量是一种特殊的变量,用于存放其他变量的内存地址,通过指针可以间接访问和修改该变量的值。指针与指针变量的主要区别在于:指针是一个泛指的概念,而指针变量是具体的实现形式。
|
22天前
|
C语言
C语言指针(3)
C语言指针(3)
11 1
|
22天前
|
C语言
C语言指针(2)
C语言指针(2)
12 1