当前位置: 首页 > news >正文

【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位环境下:

  1. a是数组名,单独放在sizeof中,计算整个数组的大小,为16;
  2. a没有单独放在sizeof中,表示首元素地址,地址在内存中的大小为4字节,a+0找到的是1的地址,为4;
  3. *a拿到的是数组首元素1,大小为4字节;
  4. a+1为元素2的地址,大小为4字节;
  5. a [ 1 ] 得到的是元素2,大小为4字节;
  6. &a为整个元素的地址,类型为数组指针 int (*)[4],指针变量的大小为4字节;
  7. 两种思路:
    *&两者相互抵消,最终得到的是a,单独放在sizeof中表示数组的大小,为16字节;
    &a得到的是整个数组的地址,类型是数组指针int (*) [ 4 ],对数组指针进行解引用得到的是数组,因此算的是整个数组的大小,为16字节;
  8. 如图, &a + 1 表示跳过整个数组的长度,即4个元素的地址,类型还是数组指针,大小为4字节
    在这里插入图片描述
  9. 取得的是第一个元素的地址,为指针变量,大小位4字节;
  10. 取得的是第二个元素的地址,为指针变量,大小为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));
  1. 数组名单独放在sizeof中,得到的是整个数组的大小,为6字节;
  2. arr没有单独放在sizeof中,表示数组首元素地址,arr+0表示得到第一个元素的地址,为指针变量 ,大小为4字节;
  3. *arr得到数组首元素 ’ a ',为字符类型,大小为1字节;
  4. arr [ 1 ] 表示数组第二个元素,为字符类型,大小为1字节;
  5. &arr表示拿到整个数组的地址,为数组指针char (*) [6] 类型,大小为4字节;
  6. &arr+1表示跳过整个数组,拿到 f 之后一个元素的地址,大小为4字节;
  7. &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))

首先初始化数组

  1. arr为首元素地址,由于不知道字符结束的位置,也就是\0的位置,因此会造成越界访问,结果是随机值;
  2. arr + 0为首元素地址,\0位置不知道,会造成越界访问,结果随机;
  3. *arr得到的是字符a,a 的ASCII码值为97,相当于把97作为地址传给strlen,这块地址不属于arr,相当于野指针,程序会报错;
  4. arr [ 1 ]是字符b,相当于把b的ASCII码值98当作地址传给strlen,也是野指针,程序报错;
  5. &arr 拿到的是数组的地址,与数组首元素地址的值相同,从字符a向后直到\0,因此也是随机值;
  6. &arr+1表示跳过整个数组,得到f后一个元素的地址,开始向后寻找\0,也是随机值;
  7. &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个元素,类型为字符

  1. arr单独放在sizeof内部,表示整个数组的大小为7字节;
  2. arr + 0 为数组首元素地址,大小为4字节;
  3. *arr得到的是数组首元素a,大小为1字节;
  4. arr [ 1 ] 为元素b,大小为1字节;
  5. &arr为整个数组地址,为数组指针变量,大小为4字节;
  6. &arr为数组的地址,+1表示跳过整个数组,还是地址,大小为4字节;
  7. &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));
  1. 表示计算数组元素的个数,\0位置在 f 后,则元素个数为6;
  2. arr+0为数组首元素地址,向后到\0,共有6个元素;
  3. *arr得到的是首元素a, 表示将a的ASCII码值97作为地址传给strlen, 相当于野指针,程序报错;
  4. 同样报错;
  5. &arr得到数组的地址,值与数组首元素的地址相同,因此从首元素a向后直到\0,共有6个元素;
  6. &arr +1 ,跳过整个数组,\0位置未知,为随机值;
  7. &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为字符指针变量,指向字符串首元素地址

  1. p为指针变量,大小为4字节;
  2. p+1为b的地址,大小为4字节;
  3. *p得到字符a,大小为1字节;
  4. p[0] == *( p + 0 ),表示得到字符串第一个元素a,大小为1字节;
  5. p为字符指针,类型为char*,&p表示字符指针p的地址,为二级指针,大小为4字节;
  6. &p+1表示跳过指针p的地址,本质还是指针,大小为4字节;
  7. &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));
  1. 6
  2. 从b开始算,共5个元素;
  3. *p为元素a,表示将a的ASCII码值97作为地址传给strlen,相当于野指针,程序报错;
  4. p [ 0 ]为元素a,表示将a的ASCII码值97作为地址传给strlen,相当于野指针,程序报错;
  5. &p表示得到一级指针p的地址,\0位置位置,为随机值;
  6. 依旧是随机值;
  7. &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位条件下:

  1. 数组名单独放在sizeof中,表示数组的大小,共12个元素,每个元素4字节,共48字节;
  2. a[ 0 ] [ 0 ] 表示第一个元素,为4字节;
  3. a[ 0 ] 表示二维数组第一行元素的数组名,数组名单独放在sizeof中表示整个数组的大小,第一行数组共4个元素,每个元素4字节,共16个字节;
  4. a[0]表示第一行数组的地址,+1表示跳过第一行数组,得到第二行数组的地址,为数组指针变量,大小为4字节;
  5. (a[0]+1)为第二行数组地址,解引用表示得到第二行数组,大小为16字节;
  6. a为数组首元素的地址,对于二维数组,首元素为第一行数组,则数组名a相当于第一行数组的地址,+1跳过第一行得到第二行数组的地址,为数组指针,大小为4字节;
  7. *(a+1)得到第二行数组,大小为16字节;
  8. a[ 0 ]表示第一行数组的数组名,&a[0]表示得到第一行数组的地址,+1跳过整个数组,得到第二行数组的地址,为数组指针,大小为4字节;
  9. *(&a[0]+1)表示得到第二行数组,大小为16字节;
  10. a相当于第一行数组的地址,*a得到第一行数组,大小为16字节;
  11. 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;
}
  1. p为结构体变量,+1跳过一个结构体,p + 0x1 == 0x100000 + 20 == 0x100014;
  2. 强制转换成无符号长整型,整型变量+1,就是正常+1,结果是0x100001;
  3. 强转为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],结合图示:

  1. p为数组指针,指向第一行数组的地址,第一行数组有4个元素,每个元素为整型
  2. p[4][2] == *(*(p+4)+2),由于p指向的数组元素只有4个,因此+1跳过的是4个元素的地址,因此p+4跳过四行数组,指向第五行数组,解引用得到第五行数组名,+2得到第五行第三个元素,因此&p[4][2]表示第五行第三个元素的地址
  3. 二维数组元素在内存中是连续存放的,p存的是a数组第一行的地址,指向的数组列数为4,那么p[ ] [ 4 ]可以认为重新将数组排列成7行4列,因此p [ 4 ] [ 2 ] == 19,a [ 4 ] [ 2 ] == 23
  4. 地址-地址得到的是两地址之间元素的个数,因此 &p[4][2] 与 &a[4][2] 之间相差4个元素,小地址减去大地址得到的是-4

对于本题

  1. %p 将-4打印成地址,-4的源码为
    10000000 00000000 00000000 00000100 源码
    111111111 111111111 111111111 111111011 反码
    10000000 00000000 00000000 00000100 补码
    计算机中存的是补码,转换成16进制为
    FFFFFFFC 就是结果
  2. 因此本题答案为 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;
}
  1. &aa+1跳过整个二维数组,强制转换成int*类型后,-1表示向前4个字节,因此,ptr1-1找到的是元素10的地址;
  2. 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]+1cpp[-1][-1] == *(*(cpp-1)-1), 三级指针cpp-1找到cp[1]的地址,*(cpp-1)找到二级指针cp[1],存的是c+2的地址,再-1找到的是c+1,字符N的地址,+1跳过一个元素,打印的是EW.


相关文章:

  • 【Linux网络编程十】网络原理之IP协议【网络层】
  • 【pytorch】torch.nn.Unfold操作
  • 【自然语言处理与大模型】模型压缩技术之量化
  • 21-算法打卡-哈希表-三数之和-leetcode(15)-第二十一天
  • Spring Data
  • AR行业应用案例与NXP架构的结合
  • C语言 数据结构 【堆】动态模拟实现,堆排序,TOP-K问题
  • 【技术派后端篇】基于 Redis 实现网站 PV/UV 数据统计
  • DeepSeek与多元工具协同:创新应用模式与发展前景探究
  • linux安装mysql数据库
  • PID控制程序编写
  • 【Linux】:UDP协议
  • 使用 WinDbg 启动程序并捕获崩溃转储的完整流程
  • MD5和sha1绕过方式总结
  • 悟空黑桃 下载地址
  • 杰理791ble配网
  • 如何安静?
  • CGAL 计算直线之间的距离(3D)
  • 小天互连与DeepSeek构建企业智能化新生态
  • 博客系统-邮件发送-nginx-服务部署
  • 商务部:支持“来数加工”等新业态新模式,发展游戏出海业务
  • “小时光:地铁里的阅读”摄影展开幕,嘉宾共话日常生活与阅读
  • 3月赴美外国游客数量加速下滑
  • 魔都眼|上海半马鸣枪:白金标运动员、“箱根之子”齐参赛
  • 尹锡悦涉嫌发动内乱案第二次庭审21日举行,媒体获准拍摄
  • 云南省交通发展投资有限责任公司原党委书记、董事长陈以东接受审查调查