练习一
#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;
}
代码分析:
一维数组,&数组名取出的是整个数组的地址,&数组名+1指针跳到整个数组的后面,此时该地址的类型为int(*)[5]
,所以在整形指针ptr的时候需要强转成int *
类型
*(a+1)
数组名没有单独放在sizeof内部,也没有取地址,此时该数组名降级变成首元素地址,*(a+1)等价于a[1]
(int *)(&a+1)
拿到整个数组后面的地址强转成int *
,此时该指针访问权限又变成4byte,ptr-1指向数组最后一个元素的地址,解引用拿出这个元素
运行结果:
练习二
#include <stdio.h>
struct Test
{
int Num;
char *pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
int main()
{
p = (struct Test*)0x00000000;
printf("%p\n", p + 1);
printf("%p\n", (long)p + 1);
printf("%p\n", (int*)p + 1);
return 0;
}
代码分析:
定义了一个结构体Test,该结构体的大小是20byte,p = (struct Test*)0x00000000
将一个十六进制的数强制类型转换成指向该结构体指针的类型,因为*p
是该结构体的实例化,p是指向该结构体的一个指针的内容,表示整个结构体开辟内存空间的起始地址
p + 1
该结构体指针+1,该指针跳过整个结构体的内存大小
(long)p + 1
将结构体指针类型的变量强制类型转换成整形,整形+1,所表示的数值+1
(int*)p + 1
将结构体指针类型的变量强制类型转换成int *
,int *
类型指针+1,向后偏移4byte
运行结果:
练习三
#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]
与练习一相同,相当于*(ptr1-1)
拿到了数组最后一个元素
(int *)((int)a + 1)
数组名a在这里降级变为首元素地址,强制类型转化成整形后+1地址向后偏移1byte
整形在内存中以小端存储,+1后又强转成int *
,此时ptr2的解引用访问的字节数为4byte,拿到了以小端存储的02 00 00 00
运行结果:
练习四(多么痛的领悟)
#include <stdio.h>
int main(int argc, char * argv[])
{
int a[3][2] = {(0,1), (2,3), (4,5)};
int *p;
p = a[0];
printf( "%d", p[0]);
}
代码分析:
注意:数组正确的初始化方式是这样
int a[3][2] = {{0,1},{2,3},{4,5}};
而该代码中的初始化方式用到了逗号表达式,逗号表达式的结果取决于最后一个表达式的结果
所以该初始化相当于如下代码:
int a[3][2] = {1,3,5};
a[0]
是二维数组第一行的数组名,在这里降级为第一行第一个元素的地址,赋给整形数组p,p[0]相当于*(p+0)
拿到第一行第一个元素
运行结果:
练习五
#include <stdio.h>
typedef int(*q)[4];
int main()
{
int a[5][5];
int(*p)[4];
p = (q)a;
printf("a_ptr=%p,p_ptr=%p\n", &a[4][2], &p[4][2]);
printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
return 0;
}
代码分析:
数组名a在此处降级为首元素地址,即第一行的地址,类型为int (*)[5]
,如果想把a赋给类型为int(*)[4]
的p存在类型差异,在vs 2010中虽在编写时有标红提示但是在运行过程中没有报错、结果正常,如需消除标红进行类型强转即可。
&a[4][2]
如图所示,拿出了第五行第三个元素的地址
&p[4][2]
因为p为数组指针,p每+1跳过一个int [4]
大小,p+4跳过了16个整形变量大小即64byte,解引用后拿出了该位置的地址,即为*(p+4)
,而*(*(p+4)+2)
此时*(p+4)
相当于int *
,表示为在*(p+4)
的地址上再往后偏移2个整形大小,解引用后拿出该位置指向的元素,&p[4][2]
再还原为该位置的地址
指针-指针=两块地址中间元素的个数
注意:两指针指向同一类型的空间时,指针-指针才有意义
如图可见俩位置间有4个整形空间大小,即16byte
因为是小地址减大地址即小数减大数结果为负,若以%p
输出,则将其看成地址,当成无符号数输出
运行结果:
练习六
#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 = *(aa + 1);
printf( "%d,%d", *(ptr1 - 1), *(ptr2 - 1));
return 0;
}
代码分析:
第一个值不再赘述,与练习一、三相同
*(aa + 1)
aa作为一个数组名在这里降级为首元素地址,即第一行的地址,是类型为int (*)[5]
的数组指针,aa+1跳过5个整形大小的空间,解引用后拿到如图所示的位置地址,此时*(aa + 1)
整体类型为int *
*(ptr2-1)
先向前偏移一个整形大小空间,解引用后拿出该位置的元素,即第一行第五个元素
运行结果:
练习七
解释下面代码:
(1)(*( void(*) ()) 0)()
(2)void (*signal(int, void (*)(int)))(int)
代码分析:
(1)
(2)
练习八
#include <stdio.h>
int main()
{
char *a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
代码分析:
a是一个指针数组,初始化个数为3,每个都是一个char *的指针,指向一个字符串的起始地址
&a+1
此时a的类型为char *(*)[3]
,+1将会跳过整个char *类型的数组
char**pa = a
a作为一个数组名,在这里降级当成首元素地址处理,第一个元素类型为为char *
,它的地址类型为char **
pa++
跳过一个char *刚好找到数组a中第二个元素,即存放第二个字符串的地址
最终到函数打印的时候,pa指向存放第二个字符串的空间位置,解引用拿到”at”的首元素地址
运行结果:
练习九
#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;
}
代码分析:
c是指针数组,其中的四个元素都是char *类型
cp也是指针数组,其中的四个元素都是char **类型
cpp是数组指针
(1) printf("%s\n", **++cpp);
(2) printf("%s\n", *--*++cpp+3);
(3) printf("%s\n", *cpp[-2]+3);
(4) printf("%s\n", cpp[-1][-1]+1);
运行结果: