指针的进化—sizeof和strlen对比(字符串和字符数组的区分)

news2025/7/19 11:12:14

1.前言

        如果你对各个数组的内容存放是什么没有个清晰的概念,对指针偏移之后的数量算不出来或者模棱两可,那么本篇就来详细介绍sizeof和strlen来具象化的显示数组的内容存放了多少内容,偏移量变化后的变化,这个数组进行运算后会不会构成越界访问......看完这篇你就懂了。

2.sizeof和strlen的对比

        sizeof,是关键字,它只计算变量所占的内存空间大小,不在乎内存存放的内容,要是把类型放进去,就会计算该类型所创建的变量所占内存的大小。需要注意的是,sizeof里面的操作数,不会真的参与计算。

        strlen,是库函数,是专门求字符串长度的,即从首字符开始,计算到“\0”之前的字符串个数,要是所创建字符串没有带有"\0",那么极有可能会存在一个问题:越界访问。因为strlen会一直往后访问直接遇到"\0"字符为止。

        这是它的函数形式。

size_t strlen ( const char * str );
strlen库函数

2.1 sizeof

#inculde <stdio.h>
int main()
{
 int a = 10;
 printf("%d\n", sizeof(a));
 printf("%d\n", sizeof a);
 printf("%d\n", sizeof(int));
 
 return 0;
}

2.2 strlen

strlen 计算的是字符串的长度,计算'\0'之前的,是库函数
int main()
{
    char arr1[3] = { 'a', 'b', 'c' };
	char arr2[] = "abc";
	printf("%d\n", strlen(arr1));//随机
	printf("%d\n", strlen(arr2));//3

	printf("%d\n", sizeof(arr1));//3
	printf("%d\n", sizeof(arr2));//4
	return 0;
}

         arr1是字符数组,不带'\0',\0'的位置是随机的,在vs2022,x86的情况下,第一个打印的是随机值,arr2数组是个字符串数组,自带'\0'字符。不相信的话可以按一下F10,打开监视窗口看看。

        那么既然strlen是返回的是开头到null终止符'\0'之前,arr1是没有'\0'的,看作一个npc一样随机刷新的,所以是随机值。arr2是有的,返回abc三个字符,sizeof返回的是内存空间大小。abc'\0',4个字符,每个1个字节,所以就是4。

arr1和arr2的数组内容

3.数组和指针题目解析

        了解,清楚sizeof和srlen的区别与使用,那么本篇文章的重点就来了,就是在不同数组类型下sizeof和strlen显示数组有多少内容,其内在还是熟悉对数组的内容存放是什么,偏移量的变化,是否构成越界访问了如指掌。上练习题,看代码来说话。

3.1一维数组 

        这里首先要说明2个规则:

  1. 数组名表示数组首元素的地址
  2. 以下两种情况下除外:

        数组名单独放在sizeof内部,表示计算的是整个数组的大小。

        &数组名,表示为取出整个数组的地址。

int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a+0));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(a[1]));
printf("%d\n",sizeof(&a));
printf("%d\n",sizeof(*&a));
printf("%d\n",sizeof(&a+1));
printf("%d\n",sizeof(&a[0]));
printf("%d\n",sizeof(&a[0]+1));

         依次来看,(a)数组名单独放在siezof内部,计算整个数组大小,这是个一维整形数组,4个元素,每个元素int类型,4个字节,4*4=16。

        第二个,(a+0) a不是单独放在内部,即使是+0也不算是单独放置,所以是首元素的地址+0,是1这个元素地址的大小,是地址,地址是指针,大小就是4/8,32位是4个字节,64位是8个字节。

        第三个,(*a)数组名没有单独放,是首元素地址,对它解引用,得到第一个元素的内容1,1是个整形,大小是4。

        第四个,(a+1)根据第二个类推,首元素地址+1,是第二个元素的地址的大小,是地址,大小就是4/8。

        第五个,很简单吧,a[1]不就是下标为1的元素2吗,对它计算大小,肯定是4了。

        第六个,(&a)对数组名进行取地址,取出整个数组的地址,计算大小,是地址,大小也是4/8.

        第七个,(*&a)取地址后解引用,取出整个数组地址,加上*,那就是得到整个数组的内容了,计算它的大小,和第一个一样,4*4=16。

        第八个,&a是整个元素的地址,&a+1,跳过这个数组到下一个数组的地址,指向数组后面的空间。这里没有构成非法访问,虽然指向的是没有初始化的空间,但是我们没有对他进行实际操作,只是访问了一下而已。

        第九个,&a[0],对第一个元素取地址,得到第一个元素的地址,是地址大小就是4/8。

        第十个,&a[0]+1,对第二个元素取地址,是地址大小就是4/8。

        如下图,在x86环境下,指针的大小是4位的,那么但凡涉及地址大小的,其实也就是指针大小,2,4,6,8,9,10都是计算指针大小,所以都是4,和我们上面的解释一样。

        在64位环境下就比较清楚了,无论什么类型的指针,大小都是8个字节,所以 2,4,6,8,9,10。是8,计算元素大小是4,整个数组大小是16,图例多了一个元素,所以显示20。

3.2字符数组

        讨论完一维整形数组,那么接下来就讨论以下字符数组,这里会分两种,一种是字符数组,一种是字符串。

3.2.1 字符串和字符数组区别

        这两者虽然只一字相差,但却是区别甚大。字符数组与字符串在本质上的区别就在于“字符串结束标志”的使用。字符数组中的每个元素都可以存放任意的字符,最后一个字符可以是‘\0’,也可以不是。但作为字符串使用时,就必须以‘\0’结束,因为很多有关字符串的处理都要以‘\0’作为操作时的辨别标志,缺少这一标志,系统并不会报错,有时甚至可以得到看似正确的运行结果,只会报个警告。但这种潜在的错误可能会导致严重的后果。程序在未遇到'\0'之前,会一直访问,所以会超出空间,形成越界访问

        那么定义字符数组的形式不是唯一的,不同情况,系统会自动或不会补'\0',没有补的话就一定要我们添加才可以,没有添加结束标志的只能看作字符,而不能看作字符串。

(1)所赋值的个数少于数组元素大小时,系统会补'\0'。形成字符串。

char c[10]={‘p’,‘r’,‘o’,‘g’,‘r’,‘a’,‘m’};

        当然你不放心也可以自己补上,但是效果是一样的 。

(2)所赋值个数和数组元素一样大。 数组里面7个字符,元素也是7个,就没有空余空间留给'\0' 来存放了。不形成字符串,是个字符数组

char c[7]={‘p’,‘r’,‘o’,‘g’,‘r’,‘a’,‘m’};

(3)当你想用单个字符赋值来决定你的数组大小时,系统是不会给你添加'\0'的,你需要自己添加上去才可以。这里会形成字符串。

char c[ ]={‘p’,‘r’,‘o’,‘g’,‘r’,‘a’,‘m’,’\0’};

         这样子则只会给你申请7个空间,不会预留位置给'\0',那么也就不能作为字符串来使用,只是一个字符数组。

char c[ ]={‘p’,‘r’,‘o’,‘g’,‘r’,‘a’,‘m’};

(4)直接赋值常量字符串,这几种都可以,不用人为去添加'\0',但是得存放空间足够大,系统会自动补上的。

char str1[15]={“I am happy”};

        以字符串常量赋值来决定数组大小,10个字符加一个'\0',一共11个字符大小。 

char str1[]=“I am happy”;

        可以去掉花括号 

char str1[15]=“I am happy”;

        但是下面这个定义就错,虽然系统会自动补'\0        ',但是因为空间不够,没有存放'\0'的位置,也构不成字符串。

char str3[10]=“I am happy”;

     3.2.2 字符数组

  来看看例题:

         一组是sizeof,一组是strlen,注意这里是没有写明数组大小,也没有看到有'\0'字符,所以构不成字符串。那么根据规则来仔细思考吧。

第一组 第一个,根据规则和sizeof的特点,计算的是整个数组的大小,共6个,每个1个字节。

            第二个,arr + 0是数组首元素的地址,大小是4/8。

            第三个,*arr是数组的首元素,计算的是首元素的大小-1字节。

            第四个,arr[1]第二个元素大小,当然是1了。

            第五个,取出的数组的地址,数组的地址也是地址,是地址大小就是4/8

            第六个,&arr+1是跳过整个,指向数组后边空间的地址,4/8。

            第七个,&arr[0] + 1是数组第二个元素的地址,是地址4/8字节。

char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr+0));
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[1]));
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(&arr+1));
printf("%d\n", sizeof(&arr[0]+1));

这里就以注释来说明了 。

char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", strlen(arr));//随机值,没有结束标识符,会一直访问直到出现'\0'
printf("%d\n", strlen(arr+0));//随机值
printf("%d\n", strlen(*arr));//strlen('a')->strlen(97),非法访问-err
printf("%d\n", strlen(arr[1]));//'b'==98,和上面的代码类似,是非法访问 - err
printf("%d\n", strlen(&arr));//&arr虽然是数组地址,但是也是从数组起始位置开始的,计算的还是随机值
printf("%d\n", strlen(&arr+1));//&arr是数组的地址,&arr+1是跳过整个数组的地址,求字符串长度也是随机值
printf("%d\n", strlen(&arr[0]+1));//&arr[0] + 1是第二个元素的地址,是'b'的地址,求字符串长度也是随机值

       由此可见,在以单个字符来赋值字符数组的时候,'\0'的添加是很有必要的,很可能就会造成越界访问导致随机值的出现。那么接下来就看看字符串。

3.2.3 字符串

        字符串,系统是自动补'\0'的,而且没有写明数组大小,所以是以字符串常量来决定数组大小的。

char arr[] = "abcdef";//数组是7个元素
printf("%d\n",strlen(arr));//6,arr是数组首元素的地址,strlen从首元素的地址开始统计\0之前出现的字符个数
printf("%d\n", strlen(arr+0));//arr + 0是数组首元素的地址,同第一个,结果是6
printf("%d\n", strlen(*arr));//*arr是'a',是97,传给strlen是一个非法的地址,造成非法访问
printf("%d\n", strlen(arr[1]));//同上
printf("%d\n",strlen(&arr));//取出的是整个数组的地址,从首元素开始算,一共6个
printf("%d\n", strlen(&arr+1));//&arr + 1是跳过数组后的地址,是没有被赋值的,统计字符串的长度是随机值
printf("%d\n", strlen(&arr[0]+1));//&arr[0]+1是b的地址,从第二个字符往后统计字符串的长度,大小是5
char arr[] = "abcdef";
printf("%d\n", sizeof(arr));//7 - 数组名单独放在sizeof内部,计算的是数组的总大小,单位是字节
printf("%d\n", sizeof(arr+0));//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字节

 3.3.4 把字符常量存放在指针

char *p = "abcdef";
printf("%d\n", sizeof(p));//p是指针变量,大小就是4/8字节
printf("%d\n", sizeof(p+1));//p + 1是b的地址,就是4/8个字节
printf("%d\n", sizeof(*p));//*p是'a',sizeof(*p)计算的是字符的大小,是1字节
printf("%d\n", sizeof(p[0]));//p[0]->*(p+0) -> *p  就同上一个,1字节
printf("%d\n", sizeof(&p));//&p是二级指针,是指针,大小就是4/8
printf("%d\n", sizeof(&p+1)); //&p + 1是跳过p变量后的地址,4/8字节
printf("%d\n", sizeof(&p[0]+1));//p[0]就是‘a’,&p[0]就是a的地址,+1,就是b的地址,就是4/8
char *p = "abcdef";
printf("%d\n", strlen(p));//6- 求字符串长度
printf("%d\n", strlen(p+1));//p + 1是b的地址,字符串长度从b到f就是5
printf("%d\n", strlen(*p));//err,*p是'a'
printf("%d\n", strlen(p[0]));//err
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] + 1是b的地址,从b的地址向后数字符串的长度是5

3.3 二维数组

        二维数组需要注意的在sizeof中,a表示什么,a[0]表示什么,a[0][0]表示什么,第一个,单独放,表示整个数组的大小,没有什么问题,第二个呢,是表示第一行的大小还是什么?a[0][0]表示的是数组第一行第一个元素应该也是没问题。

        想不明白的话,简单,用代码测一下不就能倒推了吗

int a[3][4] = {0};
printf("%d\n",sizeof(a));//48 
printf("%d\n",sizeof(a[0][0]));//4
printf("%d\n",sizeof(a[0]));//16
printf("%d\n",sizeof(a[0]+1));//4
printf("%d\n",sizeof(*(a[0]+1)));//4
printf("%d\n",sizeof(a+1));//4
printf("%d\n",sizeof(*(a+1)));//16
printf("%d\n",sizeof(&a[0]+1));//4
printf("%d\n",sizeof(*(&a[0]+1)));//16
printf("%d\n",sizeof(*a));//16
printf("%d\n",sizeof(a[3]));//16

        由显示可以看出,a[0]是二维数组中第一行的数组名,数组名是首元素地址,规则是不变的,a[0]单独放在sizeof里面,求的是整个第一行数组的大小,16个字节;而第四个a[0]作为第一行的数组名,没有单独放在sizeof内部,没有取地址,表示的就是数组首元素的地址,那就是第一行第一个元素a[0][0]的地址,a[0]+1就是第一行第二个元素的地址,是地址就是4/8个字节。其他应该都是比较熟悉的了。

        注意最后一个,感觉是越界访问了,其实没有,如果数组存在第四行,a[3]就是第四行的数组名,数组名单独放在sizeof内部,计算的是第四行的大小。

再次强调下数组名的意义:

        1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。

        2. &数组名,这⾥的数组名表示整个数组,取出的是整个数组的地址。

        3. 除此之外所有的数组名都表示首元素的地址。  

4.总结

        诸如上述的各种环境各种数组下的练习题,需要时不时就去翻看,复习,最好是不同形式的代码都手撕一遍代码,这样有助于我们复习,增加印象。

        不是常有一句话吗,自己写的代码,比较复杂的情况下,如果你没有经常看,甚至没有注释解释,第一天写完你的代码你能看懂,天也能看懂,三天,五天后,只有天知道了。虽然这是个网上的玩笑话,但是我一开始也会懒得写注释,导致一星期后我看着我好久前的代码迷惑不已,还得重新思考,然后我就开始不偷懒,勤快点好,良好的习惯需要去养成:

        一是要加注释,勤注释,增加自己理解,方便自己复习。也方便日后自己要去修改很早之前的代码的时候,能看懂自己当初写了啥是吧。二是要形成良好的书写规范,便于自己,也方便别人观摩,莫一天别人看到你的代码感到惊叹,也是一件好事呀,最后的最后祝大家新年快乐,新的一年在技术上又突飞猛进!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2290935.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

TensorFlow简单的线性回归任务

如何使用 TensorFlow 和 Keras 创建、训练并进行预测 1. 数据准备与预处理 2. 构建模型 3. 编译模型 4. 训练模型 5. 评估模型 6. 模型应用与预测 7. 保存与加载模型 8.完整代码 1. 数据准备与预处理 我们将使用一个简单的线性回归问题&#xff0c;其中输入特征 x 和标…

【memgpt】letta 课程1/2:从头实现一个自我编辑、记忆和多步骤推理的代理

llms-as-operating-systems-agent-memory llms-as-operating-systems-agent-memory内存 操作系统的内存管理

6-图像金字塔与轮廓检测

文章目录 6.图像金字塔与轮廓检测(1)图像金字塔定义(2)金字塔制作方法(3)轮廓检测方法(4)轮廓特征与近似(5)模板匹配方法6.图像金字塔与轮廓检测 (1)图像金字塔定义 高斯金字塔拉普拉斯金字塔 高斯金字塔:向下采样方法(缩小) 高斯金字塔:向上采样方法(放大)…

深入理解Java引用传递

先看一段代码&#xff1a; public static void add(String a) {a "new";System.out.println("add: " a); // 输出内容&#xff1a;add: new}public static void main(String[] args) {String a null;add(a);System.out.println("main: " a);…

925.长按键入

目录 一、题目二、思路三、解法四、收获 一、题目 你的朋友正在使用键盘输入他的名字 name。偶尔&#xff0c;在键入字符 c 时&#xff0c;按键可能会被长按&#xff0c;而字符可能被输入 1 次或多次。 你将会检查键盘输入的字符 typed。如果它对应的可能是你的朋友的名字&am…

【Rust自学】15.2. Deref trait Pt.1:什么是Deref、解引用运算符*与实现Deref trait

喜欢的话别忘了点赞、收藏加关注哦&#xff0c;对接下来的教程有兴趣的可以关注专栏。谢谢喵&#xff01;(&#xff65;ω&#xff65;) 15.2.1. 什么是Deref trait Deref的全写是Dereference&#xff0c;就是引用的英文reference加上"de"这个反义前缀&#xff0c…

吴恩达深度学习——超参数调试

内容来自https://www.bilibili.com/video/BV1FT4y1E74V&#xff0c;仅为本人学习所用。 文章目录 超参数调试调试选择范围 Batch归一化公式整合 Softmax 超参数调试 调试 目前学习的一些超参数有学习率 α \alpha α&#xff08;最重要&#xff09;、动量梯度下降法 β \bet…

【赵渝强老师】K8s中Pod探针的ExecAction

在K8s集群中&#xff0c;当Pod处于运行状态时&#xff0c;kubelet通过使用探针&#xff08;Probe&#xff09;对容器的健康状态执行检查和诊断。K8s支持三种不同类型的探针&#xff0c;分别是&#xff1a;livenessProbe&#xff08;存活探针&#xff09;、readinessProbe&#…

如何对系统调用进行扩展?

扩展系统调用是操作系统开发中的一个重要任务。系统调用是用户程序与操作系统内核之间的接口,允许用户程序执行内核级操作(如文件操作、进程管理、内存管理等)。扩展系统调用通常包括以下几个步骤: 一、定义新系统调用 扩展系统调用首先需要定义新的系统调用的功能。系统…

安卓(android)订餐菜单【Android移动开发基础案例教程(第2版)黑马程序员】

一、实验目的&#xff08;如果代码有错漏&#xff0c;可查看源码&#xff09; 1.掌握Activity生命周的每个方法。 2.掌握Activity的创建、配置、启动和关闭。 3.掌握Intent和IntentFilter的使用。 4.掌握Activity之间的跳转方式、任务栈和四种启动模式。 5.掌握在Activity中添加…

Python安居客二手小区数据爬取(2025年)

目录 2025年安居客二手小区数据爬取观察目标网页观察详情页数据准备工作&#xff1a;安装装备就像打游戏代码详解&#xff1a;每行代码都是你的小兵完整代码大放送爬取结果 2025年安居客二手小区数据爬取 这段时间需要爬取安居客二手小区数据&#xff0c;看了一下相关教程基本…

happytime

happytime 一、查壳 无壳&#xff0c;64位 二、IDA分析 1.main 2.cry函数 总体&#xff1a;是魔改的XXTEA加密 在main中可以看到被加密且分段的flag在最后的循环中与V6进行比较&#xff0c;刚好和上面v6数组相同。 所以毫无疑问密文是v6. 而与flag一起进入加密函数的v5就…

深度学习 DAY3:NLP发展史

NLP发展史 NLP发展脉络简要梳理如下&#xff1a; (远古模型&#xff0c;上图没有但也可以算NLP&#xff09; 1940 - BOW&#xff08;无序统计模型&#xff09; 1950 - n-gram&#xff08;基于词序的模型&#xff09; (近代模型&#xff09; 2001 - Neural language models&am…

家居EDI:Hom Furniture EDI需求分析

HOM Furniture 是一家成立于1977年的美国家具零售商&#xff0c;总部位于明尼苏达州。公司致力于提供高品质、时尚的家具和家居用品&#xff0c;满足各种家庭和办公需求。HOM Furniture 以广泛的产品线和优质的客户服务在市场上赢得了良好的口碑。公司经营的产品包括卧室、客厅…

【08-飞线和布线与输出文件】

导入网表后 1.复制结构图(带板宽的) 在机械一层画好外围线 2.重新定义板子形状(根据选则对象取定义) 选中对象生成板子线条形状 3.PCB和原理图交叉选择模式 过滤器选择原理图里的元器件 过滤器"OFF",只开启Componnets,只是显示元器件 4. 模块化布局 PCB高亮元…

【单细胞第二节:单细胞示例数据分析-GSE218208】

GSE218208 1.创建Seurat对象 #untar(“GSE218208_RAW.tar”) rm(list ls()) a data.table::fread("GSM6736629_10x-PBMC-1_ds0.1974_CountMatrix.tsv.gz",data.table F) a[1:4,1:4] library(tidyverse) a$alias:gene str_split(a$alias:gene,":",si…

ZZNUOJ(C/C++)基础练习1031——1040(详解版)

1031 : 判断点在第几象限 题目描述 从键盘输入2个整数x、y值&#xff0c;表示平面上一个坐标点&#xff0c;判断该坐标点处于第几象限&#xff0c;并输出相应的结果。 输入 输入x&#xff0c;y值表示一个坐标点。坐标点不会处于x轴和y轴上&#xff0c;也不会在原点。 输出 输出…

【C语言】main函数解析

文章目录 一、前言二、main函数解析三、代码示例四、应用场景 一、前言 在学习编程的过程中&#xff0c;我们很早就接触到了main函数。在Linux系统中&#xff0c;当你运行一个可执行文件&#xff08;例如 ./a.out&#xff09;时&#xff0c;如果需要传入参数&#xff0c;就需要…

深度学习练手小例子——cifar10数据集分类问题

CIFAR-10 是一个经典的计算机视觉数据集&#xff0c;广泛用于图像分类任务。它包含 10 个类别的 60,000 张彩色图像&#xff0c;每张图像的大小是 32x32 像素。数据集被分为 50,000 张训练图像和 10,000 张测试图像。每个类别包含 6,000 张图像&#xff0c;具体类别包括&#x…

【Git】初识Git Git基本操作详解

文章目录 学习目标Ⅰ. 初始 Git&#x1f4a5;注意事项 Ⅱ. Git 安装Linux-centos安装Git Ⅲ. Git基本操作一、创建git本地仓库 -- git init二、配置 Git -- git config三、认识工作区、暂存区、版本库① 工作区② 暂存区③ 版本库④ 三者的关系 四、添加、提交更改、查看提交日…