【c语言】深度理解指针4——sizeof和strlen
文章目录
- 一、sizeof和strlen对比
- 1.1. sizeof
- 1.2. strlen
- 二、指针部分练习
- 2.1. 一维数组
- 2.2. 字符数组
- 2.2.1. 代码一
- 2.2.2. 代码二
- 2.2.3. 代码三
- 2.2.4. 代码四
- 2.2.5. 代码五
- 2.2.6. 代码六
- 2.3. 二维数组
- 三、笔试题解析
- 3.1. 题目一
- 3.2. 题目二
- 3.3. 题目三
- 3.4. 题目四
- 3.5. 题目五
- 3.6. 题目六
- 3.7. 题目七
一、sizeof和strlen对比
1.1. sizeof
sizeof是单目操作符,算的是变量所占内存空间的大小,单位是字节,如果是操作类型的话,计算的是使用类型创建的变量所占内存空间的大小,且sizeof后括号内有表达式的话,是不进行计算的.
sizeof只关注类型所占内存空间的大小,并不在乎内存中是什么数据
1.2. strlen
strlen是库函数,功能是求字符串长度,原型如下:
size_t strlen ( const char * str )
返回类型是无符号整型,从str的第一个元素的地址开始向后查找,在\0之前元素的个数。
二、指针部分练习
2.1. 一维数组
int a[] = {1,2,3,4};
1 printf("%d\n",sizeof(a));
2 printf("%d\n",sizeof(a+0));
3 printf("%d\n",sizeof(*a));
4 printf("%d\n",sizeof(a+1));
5 printf("%d\n",sizeof(a[1]));
6 printf("%d\n",sizeof(&a));
7 printf("%d\n",sizeof(*&a));
8 printf("%d\n",sizeof(&a+1));
9 printf("%d\n",sizeof(&a[0]));
10 printf("%d\n",sizeof(&a[0]+1));
将a数组初始化为1 2 3 4 ,在32位环境下:
- a是数组名,单独放在sizeof中,计算整个数组的大小,为16;
- a没有单独放在sizeof中,表示首元素地址,地址在内存中的大小为4字节,a+0找到的是1的地址,为4;
- *a拿到的是数组首元素1,大小为4字节;
- a+1为元素2的地址,大小为4字节;
- a [ 1 ] 得到的是元素2,大小为4字节;
- &a为整个元素的地址,类型为数组指针
int (*)[4]
,指针变量的大小为4字节;- 两种思路:
*&两者相互抵消,最终得到的是a,单独放在sizeof中表示数组的大小,为16字节;
&a得到的是整个数组的地址,类型是数组指针int (*) [ 4 ]
,对数组指针进行解引用得到的是数组,因此算的是整个数组的大小,为16字节;- 如图,
&a + 1
表示跳过整个数组的长度,即4个元素的地址,类型还是数组指针,大小为4字节
- 取得的是第一个元素的地址,为指针变量,大小位4字节;
- 取得的是第二个元素的地址,为指针变量,大小为4字节.
2.2. 字符数组
2.2.1. 代码一
char arr[] = {'a','b','c','d','e','f'};
1 printf("%d\n", sizeof(arr));
2 printf("%d\n", sizeof(arr+0));
3 printf("%d\n", sizeof(*arr));
4 printf("%d\n", sizeof(arr[1]));
5 printf("%d\n", sizeof(&arr));
6 printf("%d\n", sizeof(&arr+1));
7 printf("%d\n", sizeof(&arr[0]+1));
- 数组名单独放在sizeof中,得到的是整个数组的大小,为6字节;
- arr没有单独放在sizeof中,表示数组首元素地址,arr+0表示得到第一个元素的地址,为指针变量 ,大小为4字节;
- *arr得到数组首元素 ’ a ',为字符类型,大小为1字节;
- arr [ 1 ] 表示数组第二个元素,为字符类型,大小为1字节;
- &arr表示拿到整个数组的地址,为数组指针char (*) [6] 类型,大小为4字节;
- &arr+1表示跳过整个数组,拿到 f 之后一个元素的地址,大小为4字节;
- &arr [ 0 ] + 1表示拿到第二个元素的地址,大小为4字节.
2.2.2. 代码二
char arr[] = {'a','b','c','d','e','f'};
1 printf("%d\n", strlen(arr));
2 printf("%d\n", strlen(arr+0));
3 printf("%d\n", strlen(*arr));
4 printf("%d\n", strlen(arr[1]));
5 printf("%d\n", strlen(&arr));
6 printf("%d\n", strlen(&arr+1));
7 printf("%d\n", strlen(&arr[0]+1))
首先初始化数组
- arr为首元素地址,由于不知道字符结束的位置,也就是\0的位置,因此会造成越界访问,结果是随机值;
- arr + 0为首元素地址,\0位置不知道,会造成越界访问,结果随机;
- *arr得到的是字符a,a 的ASCII码值为97,相当于把97作为地址传给strlen,这块地址不属于arr,相当于野指针,程序会报错;
- arr [ 1 ]是字符b,相当于把b的ASCII码值98当作地址传给strlen,也是野指针,程序报错;
- &arr 拿到的是数组的地址,与数组首元素地址的值相同,从字符a向后直到\0,因此也是随机值;
- &arr+1表示跳过整个数组,得到f后一个元素的地址,开始向后寻找\0,也是随机值;
- &arr [ 0 ] + 1,表示得到b的地址,结果也是随机值;
2.2.3. 代码三
char arr[] = "abcdef";
1 printf("%d\n", sizeof(arr));
2 printf("%d\n", sizeof(arr+0));
3 printf("%d\n", sizeof(*arr));
4 printf("%d\n", sizeof(arr[1]));
5 printf("%d\n", sizeof(&arr));
6 printf("%d\n", sizeof(&arr+1));
7 printf("%d\n", sizeof(&arr[0]+1));
"abcdef"为字符串结尾是\0,共有7个元素,类型为字符
- arr单独放在sizeof内部,表示整个数组的大小为7字节;
- arr + 0 为数组首元素地址,大小为4字节;
- *arr得到的是数组首元素a,大小为1字节;
- arr [ 1 ] 为元素b,大小为1字节;
- &arr为整个数组地址,为数组指针变量,大小为4字节;
- &arr为数组的地址,+1表示跳过整个数组,还是地址,大小为4字节;
- &arr[0]+1表示b的地址,大小为4字节;
2.2.4. 代码四
char arr[] = "abcdef";
1 printf("%d\n", strlen(arr));
2 printf("%d\n", strlen(arr+0));
3 printf("%d\n", strlen(*arr));
4 printf("%d\n", strlen(arr[1]));
5 printf("%d\n", strlen(&arr));
6 printf("%d\n", strlen(&arr+1));
7 printf("%d\n", strlen(&arr[0]+1));
- 表示计算数组元素的个数,\0位置在 f 后,则元素个数为6;
- arr+0为数组首元素地址,向后到\0,共有6个元素;
- *arr得到的是首元素a, 表示将a的ASCII码值97作为地址传给strlen, 相当于野指针,程序报错;
- 同样报错;
- &arr得到数组的地址,值与数组首元素的地址相同,因此从首元素a向后直到\0,共有6个元素;
- &arr +1 ,跳过整个数组,\0位置未知,为随机值;
- &arr[0]+1 从字符b开始,共有5个元素;
2.2.5. 代码五
char *p = "abcdef";
1 printf("%d\n", sizeof(p));
2 printf("%d\n", sizeof(p+1));
3 printf("%d\n", sizeof(*p));
4 printf("%d\n", sizeof(p[0]));
5 printf("%d\n", sizeof(&p));
6 printf("%d\n", sizeof(&p+1));
7 printf("%d\n", sizeof(&p[0]+1))
"abcdef"为字符串常量,不能被更改
p为字符指针变量,指向字符串首元素地址
- p为指针变量,大小为4字节;
- p+1为b的地址,大小为4字节;
- *p得到字符a,大小为1字节;
p[0] == *( p + 0 )
,表示得到字符串第一个元素a,大小为1字节;- p为字符指针,类型为char*,&p表示字符指针p的地址,为二级指针,大小为4字节;
- &p+1表示跳过指针p的地址,本质还是指针,大小为4字节;
- &p[0]+1表示得到b的地址,大小为4字节.
2.2.6. 代码六
char *p = "abcdef";
1 printf("%d\n", strlen(p));
2 printf("%d\n", strlen(p+1));
3 printf("%d\n", strlen(*p));
4 printf("%d\n", strlen(p[0]));
5 printf("%d\n", strlen(&p));
6 printf("%d\n", strlen(&p+1));
7 printf("%d\n", strlen(&p[0]+1));
- 6
- 从b开始算,共5个元素;
- *p为元素a,表示将a的ASCII码值97作为地址传给strlen,相当于野指针,程序报错;
- p [ 0 ]为元素a,表示将a的ASCII码值97作为地址传给strlen,相当于野指针,程序报错;
- &p表示得到一级指针p的地址,\0位置位置,为随机值;
- 依旧是随机值;
- &p[0]+1表示得到b的地址,向后共有5个元素.
2.3. 二维数组
int a[3][4] = {0};
1 printf("%d\n",sizeof(a));
2 printf("%d\n",sizeof(a[0][0]));
3 printf("%d\n",sizeof(a[0]));
4 printf("%d\n",sizeof(a[0]+1));
5 printf("%d\n",sizeof(*(a[0]+1)));
6 printf("%d\n",sizeof(a+1));
7 printf("%d\n",sizeof(*(a+1)));
8 printf("%d\n",sizeof(&a[0]+1));
9 printf("%d\n",sizeof(*(&a[0]+1)));
10 printf("%d\n",sizeof(*a));
11 printf("%d\n",sizeof(a[3]));
在32位条件下:
- 数组名单独放在sizeof中,表示数组的大小,共12个元素,每个元素4字节,共48字节;
- a[ 0 ] [ 0 ] 表示第一个元素,为4字节;
- a[ 0 ] 表示二维数组第一行元素的数组名,数组名单独放在sizeof中表示整个数组的大小,第一行数组共4个元素,每个元素4字节,共16个字节;
- a[0]表示第一行数组的地址,+1表示跳过第一行数组,得到第二行数组的地址,为数组指针变量,大小为4字节;
- (a[0]+1)为第二行数组地址,解引用表示得到第二行数组,大小为16字节;
- a为数组首元素的地址,对于二维数组,首元素为第一行数组,则数组名a相当于第一行数组的地址,+1跳过第一行得到第二行数组的地址,为数组指针,大小为4字节;
- *(a+1)得到第二行数组,大小为16字节;
- a[ 0 ]表示第一行数组的数组名,&a[0]表示得到第一行数组的地址,+1跳过整个数组,得到第二行数组的地址,为数组指针,大小为4字节;
- *(&a[0]+1)表示得到第二行数组,大小为16字节;
- a相当于第一行数组的地址,*a得到第一行数组,大小为16字节;
- a [3]相当于得到第4行数组的地址,但是越界访问了,而sizeof计算的是类型在内存中所占的大小,不关心里面存的是什么数据,a[3]的类型还是数组指针类型,大小就是4字节.
三、笔试题解析
3.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;
}
&a位整个数组地址,+1跳过整个数组,类型位数组指针,(int*)表示强制类型转换
ptr-1指向5的地址,解引用找到元素5
a+1指向元素2的地址,解引用找到2
答案为 2 5
3.2. 题目二
//在X86环境下
//假设结构体的⼤⼩是20个字节
//程序输出的结果是啥?
struct Test
{int Num;char *pcName;short sDate;char cha[2];short sBa[4];
}*p = (struct Test*)0x100000;
int main()
{printf("%p\n", p + 0x1);printf("%p\n", (unsigned long)p + 0x1);printf("%p\n", (unsigned int*)p + 0x1);return 0;
}
- p为结构体变量,+1跳过一个结构体,p + 0x1 == 0x100000 + 20 == 0x100014;
- 强制转换成无符号长整型,整型变量+1,就是正常+1,结果是0x100001;
- 强转为unsigned int*,+1跳过4字节,结果是0x100004.
3.3. 题目三
#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;
}
第一行为逗号表达式,只有1,3,5存到了二维数组中,其余位置为0,a [0] 为第一行数组的数组名,p [0] == a[ 0 ] [ 0 ],为 1
3.4. 题目四
//假设环境是x86环境,程序输出的结果是啥?
#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;
}
解释
&p[4][2]
,结合图示:
- p为数组指针,指向第一行数组的地址,第一行数组有4个元素,每个元素为整型
p[4][2] ==
*(*(p+4)+2)
,由于p指向的数组元素只有4个,因此+1跳过的是4个元素的地址,因此p+4跳过四行数组,指向第五行数组,解引用得到第五行数组名,+2得到第五行第三个元素,因此&p[4][2]
表示第五行第三个元素的地址- 二维数组元素在内存中是连续存放的,p存的是a数组第一行的地址,指向的数组列数为4,那么
p[ ] [ 4 ]
可以认为重新将数组排列成7行4列,因此p [ 4 ] [ 2 ] == 19,a [ 4 ] [ 2 ] == 23- 地址-地址得到的是两地址之间元素的个数,因此 &p[4][2] 与 &a[4][2] 之间相差4个元素,小地址减去大地址得到的是-4
对于本题
- %p 将-4打印成地址,-4的源码为
10000000 00000000 00000000 00000100 源码
111111111 111111111 111111111 111111011 反码
10000000 00000000 00000000 00000100 补码
计算机中存的是补码,转换成16进制为
FFFFFFFC 就是结果- 因此本题答案为 FFFFFFFC -4
3.5. 题目五
#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;
}
- &aa+1跳过整个二维数组,强制转换成int*类型后,-1表示向前4个字节,因此,ptr1-1找到的是元素10的地址;
- aa为二维数组名,表示第一行数组的地址,+1找到第二行数组的地址,即元素6的地址,强制转换成int*在进行-1运算,表示向前4个字节,找到的是5的地址
所以答案是10 5
3.6. 题目六
#include <stdio.h>
int main()
{char *a[] = {"work","at","alibaba"};char**pa = a;pa++;printf("%s\n", *pa);return 0;
}
a是指针数组,存放指针的数组,共有三个元素,分别是w、a、a、的地址,a为数组首元素地址,也就是字符串“work”中’w’的地址,二级指针变量pa指向它,pa++后,指向“at”中’a’的地址,打印字符串得到的是 at.
3.7. 题目七
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
: 前置++表示先++再使用,因此cpp先++后指向的是cp [ 1 ],解引用一次得到cp[1]存的地址,为c+2,再解引用得到字符串POINT;注意:cpp现在指向的值cp[1]
2.*--*++cpp+3
: + 操作符优先级最低,因此+3最后计算. cpp先++后指向的是cp[2]的地址,解引用得到cp[2],存的是c+1的地址,然后得到(*--(c+1))
,先–,得到c,c指向的是E的地址,然后再*(c+3)
,得到字符串ER;注意:cpp现在指向的值cp[2]
3.*cpp[-2]+3
: cpp[-2] == *(cpp-2),得到cp[0],解引用找到的是c+3,指向字符F的地址,再+3向后跳过3个地址,找到S的地址,打印字符串得到ST;
4.cpp[-1][-1]+1
:cpp[-1][-1] == *(*(cpp-1)-1)
, 三级指针cpp-1找到cp[1]的地址,*(cpp-1)
找到二级指针cp[1],存的是c+2的地址,再-1找到的是c+1,字符N的地址,+1跳过一个元素,打印的是EW.
完