一、指针和数组在sizeof和strlen中的用法
(1)整形数组
printf("%d\n", sizeof(a));
//a为数组名,单独放在sizeof内部,代表整个数组,所以计算的是整个数组的大小,4*4=16;
printf("%d\n", sizeof(a + 0));
//a为数组名,没有单独放在sizeof内部,代表数组首元素地址,a+0还是表示首元素地址,
//既然计算的是地址的大小,则应该为4(32位)或者8(64位)
printf("%d\n", sizeof(*a));
//a为数组名,没有单独放在sizeof内部,代表数组首元素地址,对其解引用,拿到首元素,类型为整形计算得4
printf("%d\n", sizeof(a + 1));
//a为数组名,没有单独放在sizeof内部,代表数组首元素地址,a+1则为第二个元素的地址,地址大小为4或8
printf("%d\n", sizeof(a[1]));
//a[1]为数组第二个元素,计算的是元素大小,为整形,所以为4
printf("%d\n", sizeof(&a));
//&a,取出的是整个数组的地址,计算的是数组地址的大小,既然是地址,应该为4或8
printf("%d\n", sizeof(*&a));
//&a,取出的是整个数组的地址,对其解引用,找到整个数组,计算的是整个数组的大小,所以为16。实际上也可以理解为&操作符和*操作符相互抵消,实际上为sizeof(a)
printf("%d\n", sizeof(&a + 1));
//&a,取出整个数组的地址,&a+1指向整个数组后面的一个地址,计算的也为地址的大小,所 以为4或8虽然后面的空间并没有分配给我们,但是任然存在,sizeof并没有对其访问,不会越界访问
printf("%d\n", sizeof(&a[0]));
//a[0]为数组首元素,&a[0],取出首元素的地址,计算的还是地址大小,为4或8
printf("%d\n", sizeof(&a[0] + 1));
//&a[0]为数组首元素地址,+1指向数组第二个元素的地址,计算地址大小,为4或8
结果:
(2)字符数组 sizeof
printf("%d\n", sizeof(arr));
//arr为数组名,单独放在sizeof内部,计算的是整个数组的大小,为6
printf("%d\n", sizeof(arr + 0));
//arr为数组名,并没有单独放在sizeof内部,代表首元素地址,加0还是首元素地址,计算的地址大小,所以为4或8
printf("%d\n", sizeof(*arr));
//arr为数组名,没有单独放在sizeof内部,代表首元素地址,对其解引用访问第一个元素,计算的是第一个元素的大小,为1
printf("%d\n", sizeof(arr[1]));
//arr[1]为数组第二个元素,所以计算的是数组第二个元素的大小,为1
printf("%d\n", sizeof(&arr));
//&arr为取出的整个数组的地址,但也是计算地址的大小,为4或8
printf("%d\n", sizeof(&arr + 1));
//&arr为整个数组的地址,加1跳过整个数组,计算的是数组后面地址的大小,为4或8
printf("%d\n", sizeof(&arr[0] + 1));
//&arr[0]为取出的数组第一个元素的地址,加1指向第二个元素,计算第二个元素的地址打小,为4或8
结果:
(3)字符数组 strlen
该字符数组中只初始化6个字符参数,并没有在末尾初始化“\0”
printf("%d\n", strlen(arr));
//arr为数组名,代表首元素的地址,strlen将计算数组内元素个数,由于数组内没有初始化"\0",所以strlen函数不知道什么时候停止,为随机值
printf("%d\n", strlen(arr + 0));
//arr为数组名,代表数组首元素地址,加0还是数组首元素地址,计算数组元素,无"\0",为随机值
printf("%d\n", strlen(*arr));
//arr为数组名,代表首元素地址,对其解引用拿到数组第一个元素内容,字符‘a’,ASCII码值为97,但是strlen函数接收的为地址,它将把97当作地址访问,形成越界访问,为错误代码
printf("%d\n", strlen(arr[1]));
//arr[1]为数组第二个元素的内容,字符‘b’,错误和上一个一样,错误代码
printf("%d\n", strlen(&arr));
//&arr为整个数组的地址,数组地址也有起始位置,即数组首元素地址的位置,strlen从这里进行访问计算元素个数,还是随机值
printf("%d\n", strlen(&arr + 1));
//&arr+1 将跳过整个数组,实际为数组后面位置的地址,计算结果为随机值,但是会比上述的随机值少6
printf("%d\n", strlen(&arr[0] + 1));
//&arr[0]+1 实际是指向的数组第二个元素的地址,计算结果依然为随机值。
该结果屏蔽了第三和第四个错误代码 :
(4)字符串
printf("%d\n", sizeof(arr));
//计算整个数组的大小,注意,此时数组内容并不是简单的abcdef,后面还有一个隐藏的‘\0’,因此为7个元素的大小,所以为7
printf("%d\n", sizeof(arr + 0));
//arr此时为首元素地址,arr+0依然为首元素地址,地址大小为4或8
printf("%d\n", sizeof(*arr));
//*arr找到数组第一个元素,计算的是第一个元素的大小,所以为1
printf("%d\n", sizeof(arr[1]));
//arr[1]为数组第二个元素,计算的还是元素大小,为1
printf("%d\n", sizeof(&arr));
//&arr代表数组的地址,计算的是整个数组地址的大小,还是4或8
printf("%d\n", sizeof(&arr + 1));
//&arr+1为指向整个数组后面的一个地址,计算的还是地址大小,为4或8
printf("%d\n", sizeof(&arr[0] + 1));
//&arr[0]为数组首元素地址,加1指向第二个元素的地址,本质还是求地址大小,为4或8
printf("%d\n", strlen(arr));
//arr为数组名,实际为首元素地址,计算的为数组元素个数,‘\0’之前有6个元素,为6
printf("%d\n", strlen(arr + 0));
//arr+0指向的还是第一个元素的地址,计算的还是数组元素个数,为6
printf("%d\n", strlen(*arr));
//*arr得到数组首元素内容,但是strlen需要得是地址,这将导致错误访问,程序崩溃。
printf("%d\n", strlen(arr[1]));
//和上一个同样得错误,arr[1]为数组第二个元素得内容,并非地址。
printf("%d\n", strlen(&arr));
//&arr表示整个数组的地址,但是也有起始位置,strlen将从起始位置进行访问,实际计算的还是整个数组元素个数,为6
printf("%d\n", strlen(&arr + 1));
//&arr+1 将跳过整个数组,指向数组后面的地址,去寻找下一个‘\0’,所以是随机值
printf("%d\n", strlen(&arr[0] + 1));
//&arr[0]+1指向的是数组第二个元素的地址,因此元素个数为6-1=5
结果:
依然为除去了错误代码之后的结果
(5)字符指针
printf("%d\n", sizeof(p));
//p为指针,sizeof计算的为指针的大小,为4或8
printf("%d\n", sizeof(p + 1));
//p为指向字符串首元素的地址,加1跳过一个元素地址,指向第二个元素的地址,实际计算也是地址大小,为4或8
printf("%d\n", sizeof(*p));
//对p解引用拿到字符串首元素的内容,计算的为元素大小,为1
printf("%d\n", sizeof(p[0]));
//p[0]== *(p+0)== *p 所以计算的还是字符串首元素内容的大小,为1
printf("%d\n", sizeof(&p));
//p为一级指针,指向字符串首元素地址,&p为p在内存中的地址,计算的还是地址大小,为4或8
printf("%d\n", sizeof(&p + 1));
//&p取出p在内存中的地址,加1跳过p的地址,指向p后方的地址,计算地址大小为4或8
printf("%d\n", sizeof(&p[0] + 1));
//&p[0]== &(*(p+0))实际为指向‘a’的地址,计算地址大小还是4或8
printf("%d\n", strlen(p));
//p为a的地址,计算的为字符串的元素个数,为6
printf("%d\n", strlen(p + 1));
//p+1 为b的地址,计算的是从b开始还有多少个元素,为5
printf("%d\n", strlen(*p));
//错误代码,*p为‘a’,并非指针
printf("%d\n", strlen(p[0]));
//同样错误,p[0]==*(p+0),实际上还是a
printf("%d\n", strlen(&p));
//&p为p在内存中的地址,无法知道这个地址后面的内容,所以为随机值
printf("%d\n", strlen(&p + 1));
//&p+1为跳过p之后的地址,还是随机值
printf("%d\n", strlen(&p[0] + 1));
//&p[0]为a的地址,加1为b的地址,计算的为从b开始到‘\0’的元素个数,为5
结果:
依然为除去了错误代码之后的结果
(6)二维数组
printf("%d\n", sizeof(a));
//a为数组名,单独放在sizeof内部,表示整个数组,计算的为整个数组的大小,为48
printf("%d\n", sizeof(a[0][0]));
//a[0][0]为数组首元素,计算的为数组元素的大小,为4
printf("%d\n", sizeof(a[0]));
//a[0]表示二维数组的第一行,为第一行的数组名,计算的为第一行的大小,为16
printf("%d\n", sizeof(a[0] + 1));
//a[0]为二维数组第一行的数组名,代表第一行的地址,加1为第二行的地址,计算的为地址的大小,为4或8
printf("%d\n", sizeof(*(a[0] + 1)));
//*(a[0]+1)==a[0][1]实际上为第一行第二个元素的大小,为4
printf("%d\n", sizeof(a + 1));
//a为二维数组的数组名,代表数组首元素,即第一行的地址,加1跳过第一行,指向第二行,计算的为第二行的地址,为4或8
printf("%d\n", sizeof(*(a + 1)));
//a+1为第二行的指针,对其解引用拿到第二行的数组名,计算的是第二行的大小,为16
printf("%d\n", sizeof(&a[0] + 1));
//&a[0]为取出的第一行的地址,加1跳过第一行,指向第二行的地址,计算的是地址的大小为4或8
printf("%d\n", sizeof(*(&a[0] + 1)));
//&a[0] + 1为第二行的地址,对其进行解引用拿到第二行的数组名,计算的为第二行的大小,为16
printf("%d\n", sizeof(*a));
//a为二维数组的数组名,代表首元素的地址,对其解引用拿到第一行的数组名,计算的为第一行的大小,为16
printf("%d\n", sizeof(a[3]));
//二维数组虽然只有3行,a[3]==*(a+3)实际上为第四行的数组名,计算的还是一整行的大小,为16
结果:
二、练习题
1、
题目代码:
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)意味着以整形的方式向后访问4个字节,即访问5指向的位置
a为数组首元素地址,加1代表数组第二个元素的地址,解引用拿到2
2、
题目代码:
//结构体的大小是20个字节
struct Test
{
int Num;
char* pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
int main()
{
p = (struct Test*)0x100000;
printf("%p\n", p + 0x1);
printf("%p\n", (unsigned long)p + 0x1);
printf("%p\n", (unsigned int*)p + 0x1);
return 0;
}
分析:
结果:
3 、
题目代码:
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; }
分析:
所以ptr2解引用将拿到内存中的 00 00 00 02
然后从空间中拿出来就是 2000000
结果:
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]={ 1,3,5};
剩余的元素皆为0
a[0];表示第一行数组名,也就是第一行首元素的地址
p[0]==*(p+0)实际上找到了第一个元素内容,即为1。
结果:
5、
题目代码:
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为一个5行5列的二维数组,p为一个指向数组的指针,数组有4个元素,每个元素都是整形
a代表数组首元素地址,即第一行的地址,将其存放到p中,( p+i )每次移动一步向后移动4个整形。
指针减指针实际上表示指针之间元素的个数,所以&p[4][2]和&a[4][2]之间有4个元素。
但是&p[4][2]的地址小,所以应该为-4
当然,前面所述都是%d形式打印下才是-4,
%p就不是了
-4的补码为:11111111 11111111 11111111 11111100
16进制为FF FF FF FC
结果:
6、
题目代码:
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;
}
分析:
aa为二维数组,&aa为取出的整个数组的地址,加1跳过整个数组,然后将其强制类型转换为(int*)类型再赋值给ptr1
所以ptr1为指向整个数组的后方的整形指针,减1只移动4个字节,指向数组最后一个元素,解引用,访问最后一个元素内容,打印10;、
aa为二维数组的数组名,指向第一行,加1指向第二行,解引用拿到第二行的数组名,实际上拿到第二行首元素的地址,将其强制类型转换为(int*)移动一步为4个字节,所以减1指向第一行最后一个元素,解引用拿到 5
结果:
7、
题目代码:
int main()
{
char *a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
分析:
a为一个字符指针数组,里面存放的是"work"、"at"、"alibaba"、这三个字符串的首字符地址
pa中存放的是a的数组名,也就是首元素地址,即字符串"work"首元素‘w’的地址的地址,所以类型为二级指针。
pa++则指向下一个地址,也就是存放"at"字符串首元素'a'的地址的地址
*pa找到"at"字符串首元素‘a’的地址,然后以字符串形式打印,所以打印的为 at
结果:
8、
题目代码:
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;
}
分析:
(1)
前置++,先加后用,cpp向下移动一步指向存放c+2的地址,解引用,拿到数组元素c+2,c+2也为地址,指向c数组第三个元素,再解引用拿到c数组第三个元素的内容,即POINT字符串的首元素地址
然后打印出 POINT
(2)
先进行++cpp,cpp向后继续移动一步,指向存放c+1的地址,解引用,拿到数组元素c+1,c+1也为地址,指向数组c的第二个元素,然后执行--操作符,向后移动一步,此时为指向数组c的第一个元素,再进行解引用拿到数组c的第一个元素的内容,为字符串ENTER的首元素地址,加3向后移动三步,指向字符串"ENTER"中E的地址,打印出 ER
(3)
*cpp[-2]==*(*(cpp-2)),上一次移动cpp指向c+1,减2再次指向c+3,解引用拿到c+3,c+3指向数组c的最后一个元素,在解引用拿到此元素,实际为指向字符串FIRST的首元素F的地址,加3向后移动3步,为S的地址,打印出ST
(4)
cpp[-1][-1]==*(*(cpp-1)-1) cpp-1指向了c+2,解引用拿到了c+2,c+2指向数组c的第三个元素,再减1指向了数组c的第二个元素,解引用拿到字符串NEW的首元素N的地址,加1指向E,然后打印EW
结果: