1、笔试题1:
#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; } //程序的结果是什么?
- 解析:*(ptr - 1) :
&a表示取出整个数组的地址,&a+1就跳过整个数组,&a的类型:int(*)[5],&a+1的类型还是int(*)[5],将&a+1强制类型转换为int*,赋给ptr,则ptr指向的就是&a+1的地址,ptr-1即为前一个元素的地址,再解引用*(ptr-1)拿到的就是5。
*(a + 1):
a表示数组名为首元素1的地址,+1表示元素2的地址,再解引用*(a+1)就是2。
2、笔试题2:
#include<stdio.h> struct Test { int Num; char* pcName; short sDate; char cha[2]; short sBa[4]; }*p; //假设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); printf("%x\n", p + 0x1); printf("%x\n", (unsigned long)p + 0x1); printf("%x\n", (unsigned int*)p + 0x1); //%p以地址的形式打印,不省略0 //%x就是打印16进制,省略0 return 0; }
解析:
p+0x1:
整形指针(int*)+1跳过4个字节,字符指针(char*)+1跳过1个字节,结构体指针p+1应该跳过一个结构体大小,也就是20字节,20的16进制是0x000014,加完后是0x100014。
(unsigned long)p + 0x1:
p本来是个结构体指针,把p强转成unsigned long类型,就是将变成无符号整型,就不再是指针了,既然是无符号整型,那就当成一个数字,加1后就直接加就行了,即0x100001
(unsigned int*)p + 0x1:
p被强制类型转换成整型指针,整型指针+1跳过4个字节,加4即为0x100004
3、笔试题3:
#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; }
- 解析:
- ptr1[-1]:&a取出的是数组的地址,&a+1跳过整个数组,将其强制类型转换为int*赋给ptr1,此时ptr1的地址就是&a+1,而ptr[-1]相当于*(ptr1-1),ptr1-1的地址如图所示,再解引用拿到的就是4。
- *ptr2:数组名a表示首元素地址,将其强制类型转化为整型,就是一个值,加1就是加了1个字节,需要将内存化为以字节为单位的数据,元素1就是4个字节,如果是小端,假设a的地址0x10,转成10进制就是16,加1就是17,16进制为0x11,找到ptr2的地址了,再解引用就只需再往后访问4个字节即可,打印出来即为2000000。过程如图所示
4、笔试题4:
#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; }
- 解析:
- 此题有坑,考到了逗号表达式,int a[3][2] = { (0, 1), (2, 3), (4, 5) };这句代码可以转化为int a[3][2] = { 1, 2, 5 };此数组为3行2列,a[0]是第一行的数组名,数组名表示首元素地址,a[0]代表第一行第一个元素的地址,也就是p指向的位置,p[0] = *(p+0) = *p,p解引用拿到的就是1。
5、笔试题5:
#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; }
- 解析: &a[4][2]:
&a[4][2]表示第5行第3列的地址,如图所示。
&p[4][2]:
p是一个数组指针,p能够指向的数组是4个元素,p+1跳过4个整型,就是16个字节,p[4][2] --->*(*(p+4)+2),a是该数组数组名,即为首元素地址,将a赋给p,p也指向该地址,如图所示。p+1跳过4个元素的数组,+4的位置如图所示,*(p+4)向后找到4个元素,如黄色框框所示*(p+4),*(p+4)+2跳过2个元素指向的位置如图,再解引用,拿到的就是该元素。
&p[4][2] - &a[4][2]两个整型的地址相减拿到的就是中间的元素个数为4,因为小地址减大地址,所以%d的形式打印就是-4
而%p的形式打印就是打印地址,只是打印补码即可
-4的补码:1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1100
换成16进制打印就是FFFF FFFF FFFF FFFC
6、笔试题6:
#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; }
- 解析:
- *(ptr1 - 1):&aa表示二维数组的地址,&aa+1表示跳过整个二维数组到如图所示的位置,强转成整型指针赋给ptr1,ptr1-1的地址即如图所示,再解引用拿到的就是数字10。
*(ptr2 - 1):
aa为数组名表示二维数组首元素第一行的地址,aa+1跳过第一行来到第二行如图所示,再解引用*(aa+1)拿到第二行数组名首元素地址,即元素6的地址,强转赋给ptr2,ptr2-1向前移动再解引用拿到的就是5。
7、笔试题7:
#include <stdio.h> int main() { char* a[] = { "work","at","alibaba" }; char** pa = a; pa++; printf("%s\n", *pa); return 0; }
- 解析: pa的类型是char*,加1表示跳过了一个char*的元素,指向如图所示位置,再解引用*pa拿到a的地址,再%s向后访问到\0停止。即为at。
8、笔试题8:
#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; }
- 解析:
- **++cpp: ++cpp跳过了一个char**元素的地址到如图所示位置,解引用*++cpp拿到了c+2这个元素,c+2又是c里第3个元素的地址,再解引用拿到了p的地址,以%s的形式打印直到\0为止,即POINT。
- *-- * ++cpp + 3:++cpp再跳过一个char*的元素到图示位置,再解引用 * ++cpp拿到c+1,-- * ++cpp让c+1-1=c,此时c就是c数组名第一个元素地址,再解引用*-- * ++cpp拿到E的地址,再+3指向第二个E,再%s打印处ER。
- *cpp[-2] + 3: *cpp[-2]就是**(cpp-2)+3。cpp-2指向的位置如图所示,解引用*(cpp-2)拿到c+3,再解引用**(cpp-2)拿到F的地址,再+3指向S,再%s打印出ST。
- cpp[-1][-1] + 1: cpp[-1][-1] + 1就是*(*(cpp-1)-1)+1,cpp-1的位置如图,解引用拿到c+2,减1就是c+1,再解引用*(*(cpp-1)-1)拿到的就是N的地址,再加1指向E,%s打印出EW。