文章目录
- 笔试题 1
- 笔试题 2
- 笔试题 3
- 笔试题 4
- 笔试题 5
- 笔试题 6
- 笔试题7
- 笔试题 8
- 分析下面代码的结果为何是这样
笔试题 1
1. 笔试代码
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int* ptr = (int*)(&a + 1);
printf("%d,%d\n", *(a + 1), *(ptr - 1));
return 0;
}
2. 代码答案
3. 代码分析
- *(a + 1):*(a + 1) == a[1],所以结果是 2。
- *(ptr - 1):取出整个数组 a 的地址,数组的地址 + 1 会跳过整个数组。然后再将地址赋给 ptr,此时 ptr 实际指向的是 5 后面那个位置;让 ptr - 1 此时 ptr 指向的就是 5
笔试题 2
1. 笔试代码
- 此处结构体的大小在 32 位环境底下为 20 个字节。
struct Test
{
int Num;
char* pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
//假设p 的值为0x000000。 如下表表达式的值分别为多少?
//已知,结构体 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;
}
2. 代码答案
3. 代码分析
- p + 0x1:p 是 struct Test 类型的指针,步长为 20,指针 + 1 会向后走 20 个字节,0x 14 就是 十进制的 20。
- (unsigned long)p + 0x1:将 p 变成了一个 unsigned long 类型的变量,也就是说 p 里面存的地址变成了 1 个十进制的数 00000000,将这个数 + 1 后转换成十进制结果就是 00000001。
- (unsigned int*)p + 0x1:将 p 强转为 unsigned int* 类型的指针,使得 p + 1 向后走一步的步长变为了 4 个字节,所以结果才会是 00000004。
笔试题 3
1. 笔试代码
int main()
{
int a[4] = { 1, 2, 3, 4 };
int* ptr1 = (int*)(&a + 1);
int* ptr2 = (int*)((int)a + 1);
printf("%x,%x\n", ptr1[-1], *ptr2);
return 0;
}
2. 代码答案
3. 代码分析
- ptr1[-1]:ptr1 == (int*)(&a + 1) 和第一题一样,指向的是 4 的后面,ptr1[-1] 等于 *(ptr1 - 1),此时 ptr 指向了 4,所以打印结果为 4。
- *ptr2:此时的 a 表示的是首元素的地址,将首元素地址强转为 int 类型。此时 a 面存着的就是个整型值,整型值 + 1 那就是 + 1 了。将 + 1 后的整型值强转为 int* 赋给 ptr2,此时 ptr 就指向第一个元素的第二个字节的内容。最后以打印 *ptr2 的结果,又因为 ptr2 是个 int* 类型的指针,所以*ptr2 就是从第一个元素的 2 个字节开始向后访问 4 个字节的内容。
笔试题 4
1. 笔试代码
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
int* p;
p = a[0];
printf("%d\n", p[0]);
return 0;
}
2. 代码答案
3. 代码分析
- 这是一个三行二列的二维数组,然而要注意数组的初始化内放的其实是 3 个逗号表达式;
- p = a[0] 将第一行的地址赋给 p,p[0] 就是首行首列也就是第一个表达式的内容 1 啦。
笔试题 5
1. 笔试代码
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;
}
2. 代码答案
3. 代码分析
- 这题的二维数组如果画成多行多列的方式就不好讲了,所以直接按照二维数组在内存中存放的本质来画图。
- p 是一个数组指针,它所指向的数组应该有 4 个元素,每个元素都是 int 类型,p + 1 会跳过 4 个int 类型的数据。
- a 作为二维数组数组名,表示首元素地址,它的类型应该是 int (*)[5],然而 p 的类型却为 int(*) [5]。
- 在将 第一行的地址赋给 p 时 p 肯定时不能这么存的,但如果硬是要存的话,p 最终还是会指向 a[0][0] 的位置。
- p 虽然指向了 a[0][0] 的位置,但是 p 认为它指向的数组只有 4 个整型,对 p 解引用 或 p + 1 的时候只会向后访问 4 个整型。
3.1 &p[4][2] - &a[4][2]
- a[4][2] 很好找,p[4][2] 可以转换成 ((p + 4) + 2),接下来就是分析 p + 4 以及 *(p + 4) + 2 在哪了。
- p + 4:对于 p 来说,它所指向的数组应该只有 4 个元素,所以 p + 4 跳过的元素应该是 4 * 4 = 16,最后 p + 4 就指向了 a[3][1] 的为位置.
- *(p + 4) + 2:*(p + 4) 就是访问从 a[3][1] 开始的向后 4 个元素,所以 *(p + 4) +2 的位置实际指向 a[3][3]
-
两个指针(地址)相减的绝对值是两个地址之间的元素个数
-
用小的地址(&p[4][2])减去大的地址(&a[4][2])的结果是 -4,但是以 %p 的形式打印 -4 出来就得先分析数据在内存中存储的情况了。
11111111 11111111 11111111 11111100 //-4 的补码
将 -4 的补码以 %p 的形式打印时,&p 认为 -4 的补码是个地址,地址是没有原反补概念的。
所以 %p 会直接将 -4 的补码直接以 16 进制的形式打印出来,结果就是 FFFFFFFC
3.2 &p[4][2] - &a[4][2]
- &p[4][2] 与 &a[4][2] 之间隔着 4 个元素,所以 &p[4][2] - &a[4][2] 结果就是 -4。以 %d 打印的出来的结果自然就是 -4 了。
笔试题 6
1. 笔试代码
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\n", *(ptr1 - 1), *(ptr2 - 1));
return 0;
}
2. 代码答案
3. 代码分析
- *(ptr1 - 1):&aa 表示整个二维数组的地址,&aa + 1 指向了 10 的后面,ptr1 存着 10 后面的地址。将这个地址强转为 int* 赋给 ptr1,ptr1 的步长就变为了 4 个字节, ptr1 - 1 往前移动 4 个字节指向了 10 这个位置。
- *(ptr2 - 1):aa 表示的是首元素(第一行)的地址,aa + 1 跳过第一行指向了 第二行的地址。*(aa + 1) 相当于直接拿到了第二行,也相当于拿到了第二行的数组名。而将第二行的数组名赋给 ptr2 ,ptr2 就等于指向了 第二行的第一个元素 6。因为 ptr2 是 int* 类型的指针,ptr2 - 1 往前移动 4 个字节指向了 5 这个元素。
笔试题7
1. 笔试代码
int main()
{
char* a[] = { "work","at","alibaba" };
char** pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
2. 代码答案
3. 代码分析
- a 是一个字符指针数组,该数组存储着每个串的第一个字符的地址。
- 数组名 a 表示数组首元素(“word”)的第一个字符 ‘w’ 的地址,将 a 赋给 pa 相当于让 pa 指向了数组的 ’ w '。
- pa++ 相当于让 pa 跳过了一个 char* 的元素指向了 a[1],对 pa 解引用 通过 a[1] 的地址就找到了 a[1] 中存储的内容(“at”)了。
笔试题 8
1. 笔试代码
int main()
{
char* c[] = { "ENTER","NEW","POINT","FIRST" };
char** cp[] = { c + 3,c + 2,c + 1,c };
char*** cpp = cp;
printf("%s\n", **++cpp); //解析在 3.1
printf("%s\n", *-- * ++cpp + 3); //解析在 3.2
printf("%s\n", *cpp[-2] + 3); //解析在 3.3
printf("%s\n", cpp[-1][-1] + 1); //解析在 3.4
return 0;
}
2. 代码答案
3. 代码分析
- 整体布局图大致如下,具体怎么画的就不详细介绍了。
3.1:**++cpp
- ++cpp 指向了 cp[1],注意是对 cpp 进行 ++ 操作,此时 cpp 被改变彻底指向 cp[1]。
- 第一次解引用(*cpp)后通过 cp[1] 中存储着的 c + 2 的地址找到了 c[2]。
- 第二次解引用(**cpp)后通过 c[2] 中存储着的 ’ P ’ 的地址找到了 ’ P ’ 这个字符。
- 最后再以 %s 的形式从 ’ p ’ 开始打印字符串 “POINT”。
3.2:*-- * ++cpp + 3
- 注意:此时的 cpp 指向 cp[1] 的位置。
- 先算 ++cpp 让 cpp 指向 cp[2] 的位置,此时 cpp 被彻底改为指向 cp[2]。
- 对 ++cpp 后的结果解引用找到了 cp[2] 中存放着的 c + 1 这个值。
- 找到了 c + 1 ,让 c + 1 自减变成了 c (c[0])。c[0] 中存放着 “ENTRE” 的首字符 ’ E ’ 的地址。此时 cp[2] 中的值 c + 1 彻底被改成了 c。
- 从首字符 ’ E ’ 的地址 + 3 指向了 " ENTER " 的第二个 ’ E '。
- 最后再解引用然后 %s 形式打印从 ’ E ’ 开始的字符串 “ER”。
3.3:*cpp[-2] + 3
- 注意:此时的 cpp 指向了 cp[2] 的位置。
- *cpp[-2] + 3 等于 *(*(cpp - 2) + 3)。
- cpp[-2] 等价于 *(cpp - 2),cpp - 2 指向了 cp[0] 的位置,*(cpp -2) 即访问 cp[0] 中存放的 c + 3 的地址。此时并没有改变 cpp 本身的值,cpp 本身还是指向 cp[2]。
- *cpp[-2] :为访问 c + 3 中存放着的 “FIRST” 的首字符 ’ F ’ 的地址;
- *cpp[-2] + 3:为从 ’ F ’ 的地址开始指针 + 3 指向了 ’ S ’ 的地址。
- 最后以 %s 的形式打印从 ’ S ’ 开始的字符串 “ST”。
3.4:cpp[-1][-1] + 1
- 注意:此时的 cpp 还是指向 cp[2] 的位置。
- cpp[-1][-1] + 1 等于 *(*(cpp - 1) - 1) + 1。
- cpp[-1] 等价于 *(cpp - 1) ,cpp - 1 指向 cp[1] 这块空间。
- cpp[-1][-1] 等于 (cpp - 1) - 1,(cpp - 1) 的结果是 c + 2 这个数值,*(cpp - 1) - 1 是让 c + 2 这个数值变成 c + 1,然后再对这个值进行解引用,此时得到的值为 “NEW” 的首字符 ‘N’ 的地址。
- 从 ‘N’ 的地址 + 1,指向 ‘E’ 的地址。
- 最后以 &s 的形式打印从 ‘E’ 开始的字符串 “EW”。