指针加减整数和解引用的笔试题
笔试题1:
int a[5][5];
int(*p)[4];
p = a;printf("%p %d", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
问:打印的结果为?(分别以 %d 和 %p 的形式打印)
解析:p = a;
a是一个二维数组,a单独拿出来时是数组名,表示首元素的地址,而二维数组首元素的地址就是第一行元素的地址(也就是一个一维数组),那么这里a的类型是 int(*)[5] ,p是一个指针,这个指针的类型是 int(*)[4] ,把 a赋值给p ,那么把a赋值给p会发生截断,而p每次只能访问4个字节
解析:&p[4][2] - &a[4][2]
内存示意图:
p 每次加1只能访问4个字节,且同一空间的 指针 减去 指针 得到的是指针之间的元素个数,所以 &p[4][2] - &a[4][2] 以%d 的形式打印的结果是 -4 (因为是小地址 减去 大地址,且指针减指针没有绝对值的概念)
所以 &p[4][2] - &a[4][2] 以%d打印的结果是 -4
-4 存储在内存中补码的示意图:
以%p的形式打印,那么会把 -4的补码当作地址打印出来,这时就没有正负数之分了
-4 的补码以%p的形式打印示意图:
每4位二进制表示一位十六进制
所以 &p[4][2] - &a[4][2] 以%p的形式打印的结果是 FFFFFFFC
笔试题2:
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\n", (ptr1 - 1), *(ptr2 - 1));
问:打印的结果为?
代码解析:
aa是一个2行5列的二维数组,所以 { 1,2,3,4,5,6,7,8,9,10 } 等价于 { {1,2,3,4,5} , {6,7,8,9,10} }
解析:int* ptr1 = (int*)(&aa + 1);
&数组名,取出的是整个数组的地址,加1后指向的是数组最后一个元素的下一个位置的地址,把地址强制类型转换为 int* 类型,再用 int* 类型的ptr1接收
解析:*(ptr1 - 1)
ptr1 指向的是aa数组的最后一个元素的下一个位置,减1后指向了数组最后一个元素的首地址,再解引用,就拿到了数组的最后一个元素,且最后一个元素是10
所以 *(ptr1 - 1) 以%d的形式打印的结果是 10
解析:int* ptr2 = (int*)(*(aa + 1));
aa是数组首元素地址,而二维数组的首元素地址是第一行所以元素的地址,也就是一个一维数组,那么 aa + 1 后指向的是数组第二行的所以地址,强制类型转换为 int* 后赋值给 int* 类型的ptr2,那么此时ptr2指向的就是二维数组aa 的第二行的第一个元素的首地址,也就是元素为6的首地址
解析:*(ptr2 - 1)
因为不论是一维数组还是二维数组,存储在内存中的数据都是连续的,所以 ptr2 - 1 就指向了数组的 上一个元素的首地址,也就是 5 这个元素的首地址,再解引用,拿到的就是5这个元素
所以 *(ptr2 - 1) 以%d的形式打印的结果是 5
笔试题3:
char* a[] = { "work", "at" , "alibaba" };char** pa = a;printf("%s\n", *(pa + 1));
问:打印的结果为?
代码解析:
a是一个字符指针数组,a的类型是char**,存储的并不是常量字符串,存储的是常量字符串的首元素地址,所以存储每个元素的类型是 char*
解析:char** pa = a;
a是一个二级指针,要存储a的话,需要二级指针才能存储,所以存储到的是 char** 类型的 pa,此时的pa指向的也是 数组a 首元素的地址,也就是 字符 'w' 的首地址
解析:*(pa + 1)
pa + 1 后就指向了 数组a中的第二个元素的首地址,再通过%s的形式打印 ,打印的就是第二个常量字符串
所以 *(pa + 1) 以%s的形式打印的结果是 "at"