四、指针笔试题
1、笔试题一
下面程序的输出结果是什么:(32位平台下)
int main() { int a[5] = { 1, 2, 3, 4, 5 }; int *ptr = (int *)(&a + 1); printf( "%d,%d", *(a + 1), *(ptr - 1)); return 0; }
解析:
&a取出整个数组的地址,+1跳过整个数组,然后强制转化为int*类型后赋给int*的变量ptr,此时ptr指向的位置如图所示;
*(a+1):a代表首元素地址,+1指向第二个元素,解引用得到2;
*(ptr-1):ptr指针的类型是int*,由于指针类型决定指针解引用访问的字节个数,所以ptr-1指向第五个元素,解引用得到5;
2、笔试题二
下面程序的输出结果是什么:(32位平台下)
//已知,结构体Test类型的变量大小是20个字节 //假设p 的值为0x100000。 如下表表达式的值分别为多少? struct Test { int Num; char *pcName; short sDate; char cha[2]; short sBa[4]; }*p = (struct Test*)0x100000; int main() { printf("%p\n", p + 0x1); printf("%p\n", (unsigned long)p + 0x1); printf("%p\n", (unsigned int*)p + 0x1); return 0; }
解析:
p+0x1 <==> p+1,因为此结构体的大小为20个字节,由指针类型的意义可知,这里+1跳过20个字节(一个Test结构体的大小),十进制的20等价于十六进制的14,所以 0x100000 + 0x14 = 0x100014;
p原本代表结构体Test的地址,但是这里将其强制类型转换为size_t,那么它就变为了普通的整数,整数+1就是+1,所以 0x100000 + 0x000001 = 0x100001;
p原本代表结构体Test的地址,但是这里将其强制类型转换为unsigned int*,由于指针的类型决定了指针加减整数跳过的字节个数,所以这里+1跳过4个字节(一个整形的大小),所以 0x100000 + 0x100004 = 0x100004;
3、笔试题三
在小端机器下,下面程序的输出结果是什么:(32位平台下)
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; }
解析:
(int*)(&a + 1):和第一题类似,&a的地址,然后+1跳过整个数组,此时a指向数组后面紧挨的空间(如图所示),然后强制转化为int*类型后赋给int*的变量ptr1;
(int*)((int)a + 1):a代表首元素的地址,将a强转为int类型,+1之后强转赋给int*类型的的ptr2,因为这里的a被强转为整数,所以+1跳过一个字节,此时ptr2的指向如图所示;
ptr1[-1] <==> *(ptr1-1):因为ptr1是是整形指针,所以ptr1-1的指向如图,解引用访问一个整形大小,得到a[3] = 4;
*ptr2:如图,ptr2此时指向a[0]的第二个字节,因为ptr2是整形指针,所以解引用访问图中紫色区域,得到 0x02000000(数据以小端存储的模式存入,也会已小端存储的模式拿出);
对于大小端字节序的理解\概念有问题的同学,可以看看我之前的文章:深度剖析数据在内存中的存储
4、笔试题四
下面程序的输出结果是什么:(32位平台下)
int main() { int a[3][2] = { (0, 1), (2, 3), (4, 5) }; int *p; p = a[0]; printf( "%d", p[0]); return 0; }
解析:
这里有一个陷阱:二维数组初始化的内容是三个逗号表达式,所以其实a的初始化内容是 { 1,3,5 },其余自动初始化为0,所以p[0]为1;
5、笔试题五
下面程序的输出结果是什么:(32位平台下)
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 = a:a是二维数组的数组名,代表首元素的地址,而二维数组的首元素是第一行,所以a的类型是 int (*)[5],这里我们把a赋给p,p的类型是 int (*)[4],二者类型不同;
虽然p和a的类型不一样,但是由于二维数组和一维数组一样,在空间中都是连续存储的,所以这里并不会报错,只是会报一个警告;
所以&p[4][2]取出的是二维数组第18个元素的地址,&a[4][2]取出的是二维数组第22个元素的地址,地址相减得到的是相差元素的个数,即-4;
在内存中存储的是-4的补码即11111111 11111111 11111111 11111100,同时这里以%p(地址)的形式打印,由于地址是无符号数,所以直接取出-4的补码,即十六进制的FFFF FFFFC;
6、笔试题六
下面程序的输出结果是什么:(32位平台下)
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; }
解析:
(int*) (&aa + 1):&aa取出整个二维数组的地址,+1跳过整个二维数组,然后强转为int*类型赋给ptr1;
(int *) (*(aa + 1)):aa代表首元素地址,二维数组首元素是第一行,+1跳过一行得到整个第二行的地址,然后解引用得到第二行,也相当于得到第二行的数组名,而数组名又代表首元素地址,所以*最终得到的是aa[1][0]的地址,之后再强转赋给ptr2;
*(ptr1 - 1), *(ptr2 - 1):ptr1 和 ptr2 都是整形指针,-1减去四个字节,ptr1指向aa[1][4],解引用得到10,ptr2指向aa[0][4],解引用得到5;
7、笔试题七
下面程序的输出结果是什么:(32位平台下)
int main() { char* a[] = { "work","at","alibaba" }; char** pa = a; pa++; printf("%s\n", *pa); return 0; }
解析:
如图:
a是一个指针数组,其中a[0]存放的是字符’w’的地址,a[1]存放的是字符’a’的地址,a[2]存放的是字符’a’的地址;
pa = a:a代表首元素的地址,而首元素是字符’w’的地址,所以pa是一个二级指针,指向a[0],pa++指向a[1];
*pa:对pa解引用得到a[1],a[1]中存放的是字符’a’的地址,以字符串的形式打印得到"at";
8、笔试题八
下面程序的输出结果是什么:(32位平台下)
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; }
解析:
各变量的指向关系如图:
**++cpp:++cpp指向cp[1],解引用找到cp[1],再解引用找到字符’P’的地址,以字符串的形式打印得到’POINT’,此时由于自增操作符的副作用,cpp指向cp[1];
*-- * ++cpp + 3:++cpp指向cp[2],解引用找到cp[2]中的内容c+1,前置–使得cp[2]中的内容变为c,此时cp[2]不再指向字符’N’的地址,而是指向字符’E’的地址;解引用找到字符’E’的地址,+3找到字符串’ENTER’中第二个’E’的地址,以字符串的形式打印得到’ER’;此时由于自增操作符的副作用导致cpp指向cp[2],cp[2]指向字符’E’;
*cpp[-2] + 3 <==> *(*(cpp-2)) + 3:cpp-2指向cp[0],解引用找到cp[0]中的内容c+3,再解引用找到字符’F’的地址,+3找到字符串’FIRST’中字符’S’的地址,以字符串的形式打印得到’ST’;此时因为cpp-2并没有副作用,所以cpp仍然指向cp[2];
cpp[-1][-1] + 1 <==> *(*(cpp - 1) - 1) + 1:cpp-1指向cp[1],解引用找到cp[1]中的内容c+2,再-1变为c+1,解引用找到c+1处的内容,即字符’N’,+1找到字符串"NEW"中的字符’EW’;此时cpp仍然指向cp[2],cp[1]中的内容仍然为c+2;