个人成就
- 发布了50篇内容
- 获得了4次赞同
- 获得了3次收藏
个人简介
擅长领域
暂时没有设置哦~
-
STM32学习回忆录---------第3次写流水灯程序
这是我第3次用STM32写流水灯程序了,感触颇深,想和大家分享我学习STM32的故事。
初次相识STM32
2008年春天的第一场雪,来的比往年更大一些,这个春节,我留在深圳,无法回到河南。我在街上乱逛,无意间看到公交站台上的广告,深圳会在春季举办IIC (国际集成电路)大会,那是我刚刚大学毕业,学的是芯片制造专业,所以我决定参加。
到那一天,在会展上,我看到了国外IC的先进制程,心想中国再过几年也会达到这个水平吧。
后来,我看到了一个蝴蝶展厅,那是STM32展馆,我感觉这个芯片不错。这是我和STM32的初次相识。
几年以后,STM32在国内迅速火了起来,因为我的专业在国内很难找到工作,我决定学习STM32。
第1次学习STM32
培训地点: 河南郑州,时间: 2013年,学习方式:线下。
这是我第1次用STM32写流水灯,也是第一次用库函数,使用的芯片是STM32F103,用库函数的方式写流水灯,终于告别了寄存器,当时认为这是一种先进的方法,因为使用起来相当方便,想着以后单片机就不用查寄存器了,这是单片机历史上的伟大跨越。这个老师的教学方式是直接使用库函数,没有讲过库函数。在学习中,查错是一件让我头痛的事情,没有用过单步仿真,出现问题不知道,错在哪里,库函数固然好用,但库函数无法找出错误。使用printf(),有效果,但效果有限。我的这个老师非常诚实,我有不会的问题,他能给我解决就解决了,解决不了,老师就会说:“这个我也不会” !
第2次学习STM32
培训地点: 广东深圳,时间: 2016年,学习方式:线下。
这是我第2次用STM32写流水灯,也是用库函数的方式,使用的芯片是STM32F103。这个老师的水平要高一些,他把用到的库函数都讲了一遍,但是很少讲解寄存器。当我写流水灯程序的时候,我不仅会用库函数,而且还能够看懂库函数。当时觉得官方的库函数写的太好了,我不禁为官方点赞!。我问老师,这个启动代码能不能讲一下,老师说:你会用就行了,启动代码官方已经为你写好了,你不用知道为什么,你需要知道哪个是F1 的启动代码就可以了。出现错误的时候,依然没有查看寄存器和变量,用的是printf(),来进行调试,排查错误还是让人头疼。
4月份的时候,我参加了STM32深圳峰会,官方介绍了CubeMx ,并说这是一种比库函数还方便的图形化编程方法,我认为汇编语言过时了,用C语言查寄存器的方法也过时了,现在STM32用的是库函数,但这种方法必将被CubeMx 所代替,因为随着编程技术的不断发展,新的技术必然会取代旧的技术。
随着STM32 学习的深入,我开始学习一些高大上的东西,例如 触摸屏CAN SDIO 文件系统 USB等,难度太大了,我陷入其中,无法自拔。
第3次学习STM32
培训地点: 河南老家,时间2021年,学习方式:线上。
学了单片机两个月了,以流水灯为例,学到了前两次所没有学到的东西,因此感悟大不相同。
1. 对寄存器的操作,就是往正确的地址写或读正确的数据。有点像送快递的,找到地址送或收快递。
2. 能够被编译器发现的错误,解决比较容易,而能通过编译,却不能实现期望的功能,这种错误往往要费点时间,这个需要单步仿真,查看寄存器或变量值,此便对错误进行定位,因此必须缩小代码的范围,从工程到文件,到函数,到语句,这个有点像修电路板,从电路板到单元电路,最终找到坏的那个元器件。有些情况下,即便通过单步调试,但当整个程序运行时,还是无法实现功能。这时,还要对程序进行优化,直到实现期望的功能。自己的程序出现错误,自己检查几遍都检查不出来,但是交给别人,别人很快就能找到问题,可是问题来了,自已必须具备查错的能力,因为别人不可能总为你查错。
3.即然编译器可以把C语言翻译为汇编语言,那么写程序的人也能够这样,汇编语言有一定的难度,不是面向人的,那么就可以用面向人的C语言,去翻译成汇编语言,用C语言作为桥梁,就可以大大降低难度。不得不承认,汇编语言在一些场合,还是要用到的,无法替代,例如有的8位单片机,或者FOC中。以前,我认为学习单片机,跳过汇编语言,跳过寄存器,是一种好的方法,但事实并非如此!而操作寄存器是一种通用的方法,不受库函数的依赖,换了单片机,依旧可以很快上手!
我感觉这两个月确实收获很大,报这个单片机线上班我觉得很值,比前两个靠谱多了,因为我发现:我再也不用走弯路了!
河南学员
2021/3/26
-
C语言中的动态内存-----栈内存
C语言程序的动态内存分为栈内存区域和堆内存区域两种。栈内存是由编译器管理的,而堆内存是由程序调用具体的库函数管理的。我们今天分析下栈内存的概念。
栈内存的使用在很大程度上依赖于处理器的硬件机制。在处理器中,一般有一个寄存器来表示当前栈指针的位置,通常在内存中分配一块区域,这块内存的上界(高内存地址)和下界(低内存地址)之间是可用的栈内存区域。
栈指针是一个指向栈区域内部的指针,也就是它的值是一个地址,这个地址位于栈区的下界和栈区的上界之间。栈指针把这个栈区域分为两个部分,一个是已经使用的区域,一个是没有使用的区域。
对于栈内存的增长方向有两种:一种是向上增长的,也就是低地址向高地址增长;另一个是向下增长的,高地址向低地址增长。在目前常见的体系结构和编译系统中,栈大多是向下增长的,我们也是看下这种常见的增长形式。在初始阶段,栈指针是指向栈区间的上界。随着栈使用量的增加,栈指针的值将向低地址移动,也就是在变小。
栈内存在使用过程中有一个重要的特性是先入后出,也就是后入栈的内容将先出栈,而先入栈的后出栈。类似于一个口的瓶子,先进去的在底下,要想底下的出来就先把上面的先倒出来。栈内存的使用情况见下图:
入栈的过程和出栈的过程我们安全用图形来表示,更形象些吧~
在入栈的过程中,如果栈指针的变化超出栈内存的区域,将发生栈溢出。
从图中看出栈指针的功能是标识当前的栈位置。对栈内存处理中,每次能够获取的内容都是最后可放入栈内存的内容,而每次放入栈内存中的内容都将位于栈区域的最后。
总的来说其实栈是一个先入后出的内存区域,栈指针是提供一种硬件的内存机制。
还有一个大家可能都没听说过,或者都没关注过的,我们来一起了解一下,就是满栈和空栈的概念,我们还是通过图来形容一下,这个是由处理器的体系结构决定的。与程序的编写没有关系,甚至编译器都不需要关注这个问题。无论在哪种情况下,栈指针都是已经使用的栈区域和未使用的栈区域的分界线。
在满栈的情况:栈指针当前的位置是已经使用的栈区域。
在空栈的情况:栈指针当期的位置是没有使用的栈区域。
这个仅供大家了解下就可以了,毕竟对于我们大多数人来说都是应用者,多了解点底层的总没错,但也不必太深挖。对于栈内存的概念我就分享到这里,其实这个对于写汇编的人来说就很有用处了,或者去多读一些汇编就很能清楚栈内存的妙用了。后续我分享堆内存的一些概念,话说知识是一点点积累的过程,有时候觉得前面有的知识点懵懵懂懂的突然连起来就又通透了。这就是坚持学习的作用,希望大家都能坚持多学,才能更会用。
-
堆内存的那些事
上一篇我们分享了栈内存的概念,现在我们分享下堆内存的概念。
在一般的编译系统中,堆内存的分配方向和栈内存是相反的。当栈内存从高地址向低地址增长的时候,堆内存从低地址向高地址分配。
在C语言中,堆内存在分配和释放的时候,是程序通过调用C语言的库函数完成的。这和栈内存的分配有区别,栈内存利用的是处理器的硬件机制,而堆内存的处理使用的是库函数。
我们来看下堆内存的分配情况:
在堆内存的分配过程中,每次分配将返回一个当前分配地址的指针。在程序中如果多次分配内存,可以得到多个内存指针,每个内存指针都是本次分配内存的地址。在释放内存的时候,只需要对每个指针进行操作,那个指针所指向的内存就会被释放,而对其他的内存区域没有影响。
从内存的分配和使用上,可以看出栈内存和堆内存的区别:栈内存只有一个入口点,就是栈指针,栈内存压入和弹出的时候栈指针将发生变化,栈指针标识当前栈区域中已使用和未使用的界限,程序在访问栈内存的时候都只能通过栈指针及其偏移量;而堆内存有多个入口点,每次分配得到的指针是访问内存的入口,每个分配内存区域都可以被单独释放,程序对堆内存可以通过每次分配得到的指针访问。
堆内存有一个整体分配的过程,按照向上的堆内存分配方向。随着堆内存使用量的增加,堆内存将逐渐向高地址分配。这只是一个大体的增长的方面,在堆内存中,已使用的区域和未使用的区域是交错的,而不是像栈区域那样有明显的分界线。
堆内存的释放看下面这个图:
看到这样频繁的使用区域和释放,那么很容易看出堆内存是不连续的,跟堆内存的使用方式有关系,这个分配就相对自由灵活了,但是也是会在低地址向高地址发展的方向分配的。
比如上面释放后再分配就可以是下面两种情况:
先看再次分配1的情况:当新分配的需求比中间(刚刚释放)区域小,那么就会在紧接着的区域给分配。
再看再次分配2的情况:当新分配的需求比中间(释放的)区域大,那么只能往后寻求能给的区域。
当频繁的分配和释放内存的过程中,会很容易出现在两块已经分配的内存之间较小的未分配内存区域,这些其实可以用,但是由于他们的空间比较小,不够连续内存的分配,所以分配的时候就很难再次使用,这些较小的内存就是我们常说的内存碎片。
我们再来聊一下在C程序中堆空间的使用。
在C语言中,堆内存区域的分配和释放是通过调用库函数来完成的,实现的函数主要有四个:
void *malloc(size_t size); //分配内存空间
void free(void *ptr); //释放内存空间
void *calloc(size_t nmemb,size_t size); //分配内存空间
void *realloc(void * ptr,size_t size); //重新分配内存空间
注意:使用上面这几个函数需要包含标准库文件 <stdlib.h>
那么库函数怎么使用呢,内存分配了就要有释放,那么常用的就是malloc()和free()两个函数。malloc()函数的输入是需要分配内存的大小,输出是分配内存的指针。如果分配不成功,则返回NULL。
free()函数的输入是需要释放的指针,可以接受任何形式的指针。这个指针必须是由分配函数分配出来的。
例如:
int *pa;
pa = (int *)malloc(sizeof(int));//分配一个int大小的指针
if(NULL != pa)
{
free(pa);
}
内存使用完成需要释放,以便分配给其他程序使用。
calloc()也是内存分配的,只是可以把分配好的内存区域的初始值全部设置为0。还有这个分配内存有两个参数,第一个是分配单元的大小,第二个是要分配的数目。
malloc(sizeof(unsigned int)*10); == calloc(sizeof(unsigned int),10)
realloc()有两个参数,一个是指向内存的地址指针,一个是要重分配内存的大小,返回值是指向所分配内存的指针。
1、当参数指针为NULL的时候,作为malloc使用,分配内存
2、当重分配内存大小为0的时候,作为free使用,释放内存
3、当指针和重分配内存大小均不为0的时候,根据指针指向的堆内存区域的情况和指针大小重新分配内存。
对于realloc()作为重新分配内存的时候,有三种可能出现:
1、缩小内存
2、扩大内存,不需要移动指针
3、扩大内存,需要移动指针(指定内存区域大小不够)
在堆内存的管理上,主要容易出现以下几个问题:
1、开辟的内存没有释放,造成内存泄漏(系统不会释放任何用户分配的内存)
2、野指针被使用或释放(内存释放后,需要将内存指针置为NULL)
3、非法释放指针(分配了有效内存才存在释放,否则是非法的)
在C语言语法的方面对栈内存和堆内存如何使用没有限制。然后从使用的角度,栈内存更适用于容量较小的单个变量(例如:C语言的基本变量类型、较小的结构体和数组),堆内存则适用于开辟较大块的内存。栈内存由编译器分配和释放,堆内存由程序员分配和释放。
-
常见的内存错误及对策
对于用C或C++除了考虑上层应用,还需要考虑底层的内存管理,或者说内存泄漏的问题。
1、指针没有指向一块合法的内存
定义了指针变量,但是没有为指针分配内存,即指针没有指向一块合法的内存。
①结构体成员指针未初始化
定义一个结构体变量,但是结构体内部定义了指针成员,往往应用结构体变量的时候如果不给这 个成员指向一个合法的地址只是给成员分配了字节数,应用的时候对应内存的区域指针成员是无权 访问的,所以需要对结构体分配内存,结构体成员中指针变量也要分配内存,否则访问不到有效地 址。
②没有为结构体指针分配足够的内存
分配内存的时候,分配的内存大小不合适,比如开辟内存空间时sizeof(struct stu)误写为sizeof(struct stu *),书写错误会导致开辟空间不正确。
③函数的入口校验
不管什么时候,我们使用指针之前一定要确保指针是有效的。一般在函数入口处使用 assert (NULL !=p)对参数进行校验。在非参数的地方使用if(NULL != p)来校验。但这都有一个要求,也就 是p在定义的同时被初始化为NULL,如果没有被初始化为NULL,那么校验也起不了作用,没有被 初始化的指针变量,内部是一个非NULL的乱码
assert是一个宏,而不是函数,包含在assert.h头文件中。如果其后面括号里的值为假,则程序终 止运行,并提示出错;如果后面括号里的值为真,则继续运行后面的代码。
2、为指针分配的内存太小或内存访问越界
为指针分配了内存,但是内存大小不够,导致出现越界错误。
通常这种问题都会出现在我们容易忽略的字符串常量中,往往会忘记结束标志“