2019-03-31 21:39:59 qq_37002361 阅读数 134
  • 51单片机综合小项目-第2季第4部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第2季第4个课程,也是51单片机学完之后的一个综合小项目,该项目运用了开发板上大多数外设设备,并将之结合起来实现了一个时间、温度显示以及报警功能、时间调整功能等单片机控制常见的功能,有一定代码量,需要一定调试技巧和编程能力来完成,对大家是个很好的总结和锻炼,并且能拓展项目经验。

    3402 人正在学习 去看看 朱有鹏

本文将

进程的地址空间(数据段、代码段、堆、栈等)

C语言的变量类型与存储(常量、自动变量、静态局部变量、全部变量)

单片机中FLASH、SRAM

单片机程序编译时的Code、RO-data、RW-data、ZI-data大小

等结合的到一起考虑。基本知识摘自书籍、网络,主要提供交叉部分的提示和反复修改示例代码的演示。

 

 

在操作系统中运行C程序还涉及到命令行参数和环境变量,单片机上主要如下:

1、栈区(stack),栈是由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。栈的申请是由系统自动分配,如在函数内部申请一个局部变量 int h,同时判别所申请空间是否小于栈的剩余空间,如若小于的话,在堆栈中为其开辟空间,为程序提供内存,否则将报异常提示栈溢出(单片机上没提示,数据乱了程序会有莫名其妙的错误并进入Hard Fault)。    

2、堆(heap)区,堆一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。堆的申请是由程序员自己来操作的,在C中使用malloc函数,而C++中使用new运算符,但是堆的申请过程比较复杂:当系统收到程序的申请时,会遍历记录空闲内存地址的链表,以求寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,此处应该注意的是有些情况下,新申请的内存块的首地址记录本次分配的内存块大小,这样在delete尤其是 delete[]时就能正确的释放内存空间。

3、数据区(静态区) (static),全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。另外文字常量区,常量字符串就是放在这里,程序结束后有系统释放。

4、程序代码区,放着函数体的二进制代码。

写入ROM的是数据和机器指令,机器指令由操作码+操作数构成(有的没有操作数)。此时不再有任何C语言关键字、运算符、变量,汇编语言指令助记符、伪指令,编译器指令等。编译器先完成翻译工作,其中包括优化(C代码与汇编代码并不容易对应上),一个.c文件转换成一个.o文件,链接的过程进行地址分配,.o文件中的地址还是相对地址,链接后将是实际地址,操作数主要是立即数、寄存器、内存地址(采用精简指令集的寻址方式少),C语言中变量都是对应内存地址。

在PC中各区的地址是虚拟内存的地址,利用程序的局部性原理可以运行比内存空间大的程序,采用段页式存储,完整程序是储存在硬盘(外设)上的,根据需要复制其中一部分到内存运行,运行中可能需要进行段页切换。

单片机中使用实际物理地址(对单片机主程序来说只有一个进程),写入ROM(FLASH)的包括全部数据和代码,单片机启动过程进行加载,将可读可写的数据复制到SRAM中。指令读取从FLASH,数据读写从SRAM。(这种指令和数据储存在不同位置的叫哈弗结构,但有些只是存储位置不同访问时使用同一总线,部分单片机访问总线也是分开的使访问数据和访问指令可以并行处理,提高运行效率)

单片机的CPU内核不能任意直接写FLASH某个存储单元的内容,所以对CPU它是只读存储器ROM,SRAM可以任意读写存储单元是随机存储器RAM。一个型号的单片机通常提供不同的FLASH+SRAM组合。

编译出来的Code、RO、RW及加载代码储存在FLASH,RW和ZI需要SRAM空间。代码量、常量、全局变量之和过多FLASH不足,全部变量、静态变量之和过多SRAM不足,自动变量过多、函数嵌套调用过多栈空间不足运行中才会发现。

int main(void)

{

}

int main(void)

{

int auto_a=999;

auto_a=auto_a;//这里是为了消除编译器告警

}

自动变量是在栈空间的,且由于编译器的优化,增加的代码对代码段、数据段都没有影响。

int global_a=0;

int main(void)

{

int auto_a=999;

auto_a=auto_a;//这里是为了消除编译器告警

}

 

 

汇编代码中为全部变量生成了标号,然而数据区大小还是没变。

 

int global_a=0;

int main(void)

{

int auto_a=999;

auto_a=auto_a;//这里是为了消除编译器告警

global_a=auto_a;

}

 

 

CODE、RO、RW均有增加,汇编中增加了代码,CODE增加不难理解,RW增加也好理解,RO的增加应该来自常量区,但增加的值不好理解。

int global_a=0;

int main(void)

{

static int auto_a=999;

auto_a=auto_a;//这里是为了消除编译器告警

global_a=auto_a;

}

RW增加,ZI减少,ZI应该是算上了局部变量,静态变量是初始化的。

 

static int global_a=0;

int main(void)

{

static int auto_a=999;

auto_a=auto_a;//这里是为了消除编译器告警

global_a=auto_a;

}

 

全部变量加static后存储无变化,但不能被外部文件访问了,从汇编代码可以看出来。

 

static int global_a=0;

int global_b=0;

 

int add(int a,int b)

{

int temp;

temp=a+b;

temp++;

return temp;

}

 

int main(void)

{

//int auto_a=999;

static int auto_a=999;

auto_a=auto_a;//这里是为了消除编译器告警

global_a=auto_a;

global_b++;

//global_a=add(auto_a,global_b);

}

static int global_a=0;

int global_b=0;

 

int add(int a,int b)

{

int temp;

temp=a+b;

temp++;

return temp;

}

 

int main(void)

{

//int auto_a=999;

static int auto_a=999;

auto_a=auto_a;//这里是为了消除编译器告警

global_a=auto_a;

global_b++;

global_a=add(auto_a,global_b);

}

同一文件内函数调用被优化

 

extern int add2(int a,int b);

 

static int global_a=0;

int global_b=0;

 

int add(int a,int b)

{

int temp;

temp=a+b;

temp++;

return temp;

}

 

int main(void)

{

//int auto_a=999;

static int auto_a=999;

auto_a=auto_a;//这里是为了消除编译器告警

global_a=auto_a;

global_b++;

global_a=add(auto_a,global_b);

global_b=add2(auto_a,global_a);

while(1);

}

跳转到其它文件函数

 

 

2013-12-09 15:36:50 redline2005 阅读数 1245
  • 51单片机综合小项目-第2季第4部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第2季第4个课程,也是51单片机学完之后的一个综合小项目,该项目运用了开发板上大多数外设设备,并将之结合起来实现了一个时间、温度显示以及报警功能、时间调整功能等单片机控制常见的功能,有一定代码量,需要一定调试技巧和编程能力来完成,对大家是个很好的总结和锻炼,并且能拓展项目经验。

    3402 人正在学习 去看看 朱有鹏

1. 全局变量和静态变量存储区域和作用范围的区别

相同点:存储在静态数据区;

不同点:作用范围不同,静态变量作用范围在一个文件内,程序开始时分配空间,结束时释放空间,默认初始化为0,全局变量作用范围全局可见

 

2. 全局变量的初始化问题

全局变量的初始化必须为一个常量,不能是一个非常量的表达式;而在c++中是可以的。并且c、c++全局变量的赋值要在函数内部进行!

int a;

a = 10; //这里是对全局变量进行赋值操作,是错误的

int main(void)

{

}

在操作c和c++全局变量时,只能对其采用初始化的方式,而不能采用赋值的方式,即可以

int a = 10; //错误

而不可以:

int a;

a = 10;//这里是对全局变量进行赋值操作,是错误的

int main(void)

{

}

3. 全局变量默认初始化为0;全局指针默认初始化为NULL

 

2019-07-30 21:59:20 weixin_42876465 阅读数 911
  • 51单片机综合小项目-第2季第4部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第2季第4个课程,也是51单片机学完之后的一个综合小项目,该项目运用了开发板上大多数外设设备,并将之结合起来实现了一个时间、温度显示以及报警功能、时间调整功能等单片机控制常见的功能,有一定代码量,需要一定调试技巧和编程能力来完成,对大家是个很好的总结和锻炼,并且能拓展项目经验。

    3402 人正在学习 去看看 朱有鹏

程序运行过程中,有些数据被莫名修改了,在哪里修改的?又是怎么修改的?这个代码我只想知道是否运行过,或者运行了多少次,但是不想让程序停下来,或者仅打印调试信息,怎么办?当这个变量设置成某个数据后,我想让程序自动暂停下来进行分析,怎么办?

以上问题的所有答案就在本节内容:断点窗口(KEIL)。

本节内容将颠覆你之前对断点调试的认知。这个调试技巧鱼鹰也用了半年多了,当时知道这个调试方法的时候特别兴奋,感觉发现了新大陆。而这个调试技巧也在鱼鹰接手公司项目代码的时候快速解决了不少疑难杂症,而前些天又扩展学习了这个技巧的功能,更是让鱼鹰在学会之后轻松解决了好几个一般调试方法很难解决的 BUG,相信这个技巧也将为鱼鹰之后的开发调试之旅发挥更大的作用。

我们知道常规的断点调试是在想观察哪里的问题时就在对应的代码地址设置断点,并且一旦运行到断点位置会让程序自动暂停运行,这种断点调试功能确实为开发者解决 bug 立下了汗马功劳,但是这种方式有很大的局限性,因为很多时候我们并不需要让程序停下来,而只想知道是否在这段代码运行过,或者说发生问题的位置根本不能停下来,否则就会让整个系统功能出现问题,比如中断处理函数的调试,程序一旦停下了也就失去了所有中断的后续响应;比如两个设备通信,一方采用常规断点的方式调试,肯定会打断正常的通信过程,而这可不是我们想要的,我们只想知道在收到或发送数据后得到环境快照,而并不想让程序停下来。以上这些问题可以采用打印方式解决,但是打印调试也有很多弊端:

以串口为例:

1、你必须添加必要的打印和串口驱动代码,如果你使用 printf 函数,你还得重定向(如果对空间要求高的话,你得知道使用 printf 差不多要占用 1K 大小代码空间)。

2、如果打印效率比较低,常规波特率 9600 和 115200 打印一个字符串耗时可能比较久,那么对于中断频率较高的函数就可能就不适用了。如果你使用 printf 函数,你还得考虑函数是否可重入问题。

3、在代码中引入调试代码有风险,本来程序运行没有问题的,一旦引入调试代码之后可能就出现了问题,这种情况对于拥有丰富开发经验的人来说应该见怪不怪了。原因就在于打印输出时间太久,打乱了程序运行的节奏(而这也是我推荐使用 ITM 调试的一个原因,因为它的输出效率比串口要高得多),或者打印函数本身有问题,也会导致程序运行出现问题。

4、调试完毕之后,你必须把对应的调试代码删除(不管是删除代码还是使用宏,都要进行这一步),不然会影响运行效率。而人是健忘的(也不能说健忘,可能只是因为专注于 BUG 本身,容易忘记其它细枝末节,而解决 bug 之后的欣喜更可能忘记后续处理工作了)这个时候你可以尝试用 #warnning。但是这一步还是必不可少。

而以上问题的解决方案就是 KEIL 的断点调试窗口!

首先打开数据观察点的窗口:
在这里插入图片描述
快捷键是 Ctrl + B。
可以看到如下窗口:
在这里插入图片描述

当然你也可以通过下面这种方式打开并设置:
在这里插入图片描述
从这里你会发现,其实这个窗口就是用来管理你设置的断点的。平常使用的设置断点方法只是其中的一种特例罢了。

首先要知道的就是,调试器支持的断点数量是有限的,具体有多少视情况而定,一旦 KEIL 警告你设置断点太多,那么就要删除一些断点了:
在这里插入图片描述

常规用法

1、代码位置运行次数
有些时候我们想知道某些代码的运行次数,比如进入中断处理函数的次数,寻常的断点设置方式必然会让程序停止在中断程序中,但有些时候我们并不希望它停下来。这个时候,你只需要打开该窗口,找到已有的对应断点位置,双击之后就可以看到类似下面的窗口:
在这里插入图片描述

此时,你将 Count 的值设置的尽可能大一些,那么就可以让程序运行多次之后才停止。

比如我们设置 Count 的值为 100 次,那么必须在该代码位置运行 100 次才会让程序暂停。当你设置完后点击【Define】后,就会询问你是否需要重新定义,你选择“是”即可。
在这里插入图片描述
这样你的断点变成了这样:
在这里插入图片描述
后面的 count=100 表示剩余运行次数为 100,运行 100 次后将停止程序。前面的 00 代表断点号,E 代表这是一个执行断点,0x080016B0 代表代码地址,后面的是源码位置。

当这个断点位置运行了 2 次,重新打开该窗口(刷新数据),发现这个数变成了 98,从而可以推算出,已经运行了多少了。如果说你想让这段代码运行 2 次后停止,那么你只需要一开始设置 Count 的值为 2 即可。
在这里插入图片描述
2、数据访问
有些时候我们需要知道一些变量会在哪里被访问,那么你可以设置该变量的访问条件。比如鱼鹰想知道 emOspery 变量会在哪里被读取?那么你只需设置如下:
在这里插入图片描述

定义之后就是这样:
在这里插入图片描述
因为 Count 值设置为 1,所以每一次读取 emOspery 的操作都将使程序停止。比如这段代码:
在这里插入图片描述
还有后面的打印函数也使用 emOsprey 变量,所以也会导致程序运行停止。可能你会感到奇怪,为什么 emOsprey++这样的操作也会涉及到读取?事实上你理解了 CPU 寄存器存在的意义也就明白了。

而当你设置为写(Write)访问时,你会发现从复位程序开始运行后,程序会停止在某个地方,这是为什么?当你知道全局变量会在进入 main 函数之前被初始化时,你也就明白为什么了。

在这里我们选择使用 Objects 访问,即按整个变量对象进行访问,上面的 emOsprey 变量实际上是 uint16_t,所以 len 为 2,即字节大小。也就说,如果你设置为 Objects 访问,那么它会根据实际的情况设置访问范围。

为了更好的说明这一点,我构造一个结构体。
在这里插入图片描述
这个结构体大小可以看出是 6 个字节。
然后设置访问该结构体的条件:
在这里插入图片描述
如果我们按 Objects 访问的话,那么下面的每一条语句都会导致程序运行的停止。
在这里插入图片描述

这是因为这些数据都在 Osprey 结构体的范围内(从这里也可以了解到,只要在 len 的范围内的访问都会导致程序停止运行,所以你可以试试将 Size 设置得更大)。

而如果设置为 Byte 访问的话,那么就只有第一条语句才会导致程序停止运行:
在这里插入图片描述
实际上如果你希望只在某个结构体成员变量被访问时才停止,那么直接这么设置就可以:
在这里插入图片描述
你会发现设置是如此之简单。

实际上还有一种更为通用的访问方式,即按地址访问。
上面可以看出 Ospery.Ospery1 成员变量的地址为 0x2000 0016(由此我们知道也可以通过这个来看出一个结构体变量的地址是多少)。所以我们可以这样设置:
在这里插入图片描述
而代码位置的断点设置亦是如此。
断点太多,怎么知道程序因何停止?看你的命令窗口就知道了:
在这里插入图片描述

3、数据匹配

有些时候,我们并不关注地址访问情况,而对变量的数据内容感兴趣。比如说鱼鹰想让变量 emOspery 等于 1 时停下来,怎么设置?

在这里插入图片描述

只要简单的设置 emOspery == 1 即可(注意必须设置访问条件,并且 Size 设置正确)。

事实上你也可以设置两个变量相等作为条件:

在这里插入图片描述

设置为不等也是可以的:

在这里插入图片描述
当然还有其它支持的运算就靠你们自己去发现了(可支持运算:&,&&,<,<=,>,>= ,==,!=)。

注意:以上内容可以组合使用,比如读、写条件,计数器计数等可以同时设置。满足条件时就会让程序运行停止。

高级用法

以上为比较常规的调试功能,现在说说鱼鹰刚学习的技能,这个技能的使用灵活性更大,而且对于解决疑难杂症更是不二之选。

首先设置一个你需要的断点:

在这里插入图片描述
打开断点窗口,并双击你之前设置的断点:

在这里插入图片描述
设置 Command 为【printf(“USRAT_Init()\n”)】(注意\n,否则可能不能输出,这个应该是 KEIL 的一个 bug)。最后【Define】:

在这里插入图片描述
清空你之前的命令(如果你不嫌乱的话,也可以不清空):

在这里插入图片描述

那么你的程序每次运行到这个代码位置都会在 Command 窗口输出一条信息:

在这里插入图片描述
但是你的程序并不会停止。

如果说你想让断点代码位置运行多次之后才输出一条信息也是可以的,只要设置 Count 即可。

这里可能你会问,这 printf 不就是我们写的打印函数吗?事实上,是,也不是。

这个函数是打印函数没错,但是这是 KEIL 调用的打印函数,输出位置是 Command 窗口,和你自己写的代码没一点关系,每次触发条件时 KEIL 都会调用该函数进行打印,而不会让你的程序暂停运行。事实上断点窗口的这个 Command 绝不仅仅只是设置 printf 这么简单,如果真是这样我也不会如此推崇它了,感兴趣的可以去官网查找关于调试命令的使用方法。

因为是利用 KEIL 去执行打印任务,所以对你的程序几乎没有任何影响,并且在你设置断点后也不用担心删除代码问题,可以放心饮用。还有一个额外的好处就是,对于所有能设置调试断点的单片机都适用,因此对于调试器也就没有过多的要求了,比如说,不管你是用 JLINK、ST-LINK 还是 CMSIS-DAP(CMSIS-DAP 不能使用 ITM,所以鱼鹰才会想着用别的方式替代。总算是找到了,而且它在某些方面更出色),都可以这么用。

现在摘录官网一些关于断点窗口的知识:

表达式定义断点类型:

 当设置标志 Read 或 Write 或两者时,访问中断(A)被定义 。发生指定的内存访问时会触发断点。以字节为单位指定内存访问窗口的大小,或者以表达式的对象大小指定。对于此断点类型,Expression 必须解析为内存地址和内存类型。允许的运算符(&,&&,<。<=。>,> =,= =和!=)在程序执行暂停或执行命令之前比较变量值 。

 当 Expression 解析为代码地址时,将执行执行中断(E)。到达指定的代码地址时触发断点。代码地址必须引用 CPU 指令的第一个字节。

 当 Expression 不能简化为地址时,定义条件中断(C)。当条件表达式变为 TRUE 时,断点将触发。在每条 CPU 指令之后重新计算条件表达式,并且会大大减慢程序执行速度。

该计数值指定的次数的断点表达式必须计算为 TRUE 断点触发之前的数目。

当命令被指定的μVision 执行语句,然后恢复执行程序。此处指定的命令可以是μVision 调试或信号功能。要从这些函数中暂停程序执行,请设置系统变量 break

注意
当在模拟器中将访问断点(读或写)设置为外设寄存器(SFR)时,即使应用程序未访问外设寄存器,断点也可能触发。发生这种情况是因为μVision 模拟器在应用程序驱动和模拟器内部访问之间没有区别。
--------------------------------------------以上为引用内容--------------------------------------------------
里面有一个比较关键的就是关于条件中断(C),如果你设置的表达式不是一个代码地址,也没有设置读写访问条件,那么就会被设置为条件中断,一旦设置为条件中断,那么会在每条汇编指令后计算表达式,这会影响程序正常运行速度,所以没有必要的话,不要设置为条件中断。

**

设置断点的一般错误总结:

**
当弹出以下窗口时,说明断点设置错误,需要查看命令窗口才能知道具体错误信息。
在这里插入图片描述
a) 断点太多
删除一些断点即可

b) 重复定义断点
这是因为之前你已经定义了这个断点,而现在你又定义了这个断点,这个时候你可以选择覆盖之前的断点或者保留之前的断点

c) 不允许对同一个资源设置不同类型断点
这个是由于对同一个资源准备设置不同断点导致的,需要删除之前设置的断点才行。

d) 表达式错误
检查你的表达式是否正确,注意如果你使用了运算符,那么对于浮点变量的支持好像并不正常,不管你怎么设置,都说表达式错误。

到此,断点窗口(前期我叫它数据观察点,我也不知道从哪看到的这个词,后来觉得还是断点窗口比较准确)的内容就结束了。这个小节内容对于调试而言绝对是一大利器,也是鱼鹰决定写这个 KEIL 调试系列文章的主要原因。但是以上所有的调试内容都有一个很大的局限性,就是它只能定格在某一刻(如果你使用 Command 命令就不一样了),而这一刻前面的所有信息都无法知晓。这个时候就要了解另一个调试技能,ITM,它能将程序从出生(复位程序开始)到死亡(死循环或者断电)的大部分信息记录下来。这个章节内容早已发布,感兴趣的就去前面看一看咯。

-------------------------------2019-04-09 Osprey
-------------------------------更新 2019-04-20 Osprey

欢迎关注公众号:鱼鹰谈单片机
在这里插入图片描述

2011-12-02 10:27:49 liu_jing_yang 阅读数 571
  • 51单片机综合小项目-第2季第4部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第2季第4个课程,也是51单片机学完之后的一个综合小项目,该项目运用了开发板上大多数外设设备,并将之结合起来实现了一个时间、温度显示以及报警功能、时间调整功能等单片机控制常见的功能,有一定代码量,需要一定调试技巧和编程能力来完成,对大家是个很好的总结和锻炼,并且能拓展项目经验。

    3402 人正在学习 去看看 朱有鹏
 

SARM数据类型的具体操作方法

SARM空间是AVR单片机最重要的部分,所有的操作必须依赖该部分来完成。变量在SARM空间的存储模式有tiny ,small large 三种,也就是对应于__tiny, __near __far三中存储属性。一旦选择为哪种存储模式,对应的数据默认属性也就确定了,但可以采用__tiny, __near __far关键字来更改。
对于程序中的局部变量,编译器会自动处理的,我们也不可能加什么储存属性,但IAR提供了强大的外部变量定义。

5.1.定义变量在工作寄存器
IAR编译器内部使用了部分工作寄存器,留给用户的只有R4-R15供12个寄存器供用户使用,要使用工作寄存器必须在工程选项里打开锁定选项。
例:
定义两个变量使用工作寄存器R14,R15。
#i nclude<iom8.h>
__regvar __no_init char g @ 15;
__regvar __no_init char P @ 14;
void main(void)
{
g++;
P++;
}
在工程选项里c/c++ complier>code里打开要使用的寄存器R14-R15。
 
 
编译结果就如下,看看是不是直接使用了寄存器做为数据应用
// 4 void main(void)
main:
CFI Block cfiBlock0 Using cfiCommon0
CFI Function main
// 5 { g++;
REQUIRE ?Register_R14_is_global_regvar
REQUIRE ?Register_R15_is_global_regvar
INC R15
// 6 P++; }
INC R14
RET
注意:定义在寄存器里变量不能带有初始值。最好不要使用超过9个寄存器变量,不然可能引起潜在的危险,因为建立库的时候没有锁定任何寄存器。
5.2.定义变量的绝对地址.没有特性的变量是随机分配的,要给变量分配地址必须加以特性修饰注意在定义地址的时候千万不要和片内寄存器地址重合了。
5.2.1定义没有存储特性的绝对地址变量必须加__no_init 或者const对象特性
__no_init char t @ 0x65;//定义在I/O地址以外
const char t @ 0x65;//定义只读变量的地址
例:
#i nclude<iom8.h>
__no_init char u @ 0x65 ;
void main(void)
{u++;}
对应汇编:
void main(void)
\ main:
{u++;}
\ 00000000 E6E5 LDI R30, 101
\ 00000002 E0F0 LDI R31, 0
\ 00000004 8100 LD R16, Z
\ 00000006 9503 INC R16
\ 00000008 8300 ST Z, R16
\ 0000000A 9508 RET
5.2.2带存储特性的关键字定义变量的绝对地址__io,__ext_io定义变量在i/o空间
#i nclude<iom8.h>
__io char u @ 0x65 ;
void main(void)
{u++;}
对应汇编:
void main(void)
\ main:
{u++;}
\ 00000000 91000065 LDS R16, 101
\ 00000004 9503 INC R16
\ 00000006 93000065 STS 101, R16
\ 0000000A 9508 RET
从5.2.1和5.2.2对比,发现用5.2.2方法定义代码小多了。


5.3.关键字volatile保证从最原始的位置读取变量。在IAR编译器里,除了__no_init和__root定义的变量外,其他的类型的变量都包含有volatile和__no_init特性。

 
2017-01-02 08:56:31 qq_27522735 阅读数 808
  • 51单片机综合小项目-第2季第4部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第2季第4个课程,也是51单片机学完之后的一个综合小项目,该项目运用了开发板上大多数外设设备,并将之结合起来实现了一个时间、温度显示以及报警功能、时间调整功能等单片机控制常见的功能,有一定代码量,需要一定调试技巧和编程能力来完成,对大家是个很好的总结和锻炼,并且能拓展项目经验。

    3402 人正在学习 去看看 朱有鹏

常见的存储区域可分为:

1、栈

由编译器在需要的时候分配,在不需要的时候自动清楚的变量的存储区。里面的变量通常是局部变量、函数参数等。

2、堆

由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete。如果程序员没有释放掉,程序会一直占用内存,导致内存泄漏,在程序结束后,操作系统会自动回收。

3、自由存储区

由malloc等分配的内存块,它和堆是十分相似的,不过它是用free来释放分配的内存。

4、全局/静态存储区

全局变量和静态变量被分配到同一块内存中,在以前的C语言中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区。

5、常量存储区

这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改(当然,你要通过非正当手段也可以修改)。


例1:C语言程序

[cpp] view plain copy
  1. int x;  
  2. void main()  
  3. {  
  4. }  

变量x存储在内存哪个区域?

答:在采用段式内存管理的架构中,BSS段(bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域。

BSS是英文Block Started by Symbol的简称。BSS段属于静态存储区


例2:static全局变量与普通全局变量有什么区别?

答:static全局变量和普通全局变量存储区域相同,不同的是:

static全局变量只在声明此static全局变量的文件中有效;

普通全局变量对整个源程序都有效,当此源程序包含多于一个文件的程序时,对其他文件依然有效。


例3:static局部变量与普通局部变量的区别?

答:static局部变量的存储区为静态存储区,普通局部变量的存储区为栈;

static局部变量生存周期为整个源程序,但是只能在声明其的函数中调用,并且其值与上一次的结果有关;而普通局部变量的生存周期为声明其函数的周期,超过特定的范围其值会被重新初始化;

static局部变量如果未初始化其值默认为0,而普通局部变量则不确定。

转载自http://blog.csdn.net/xl890727/article/details/12657073

IAR RAM的操作

阅读数 3846

交叉编译和MAKEFILE

阅读数 2643

没有更多推荐了,返回首页