有趣的keil MDK细节(转)

源:有趣的keil MDK细节

1.MDK中的char类型的取值范围是?

  在MDK中,默认情况下,char 类型的数据项是无符号的,所以它的取值范围是0~255。它们可以显式地声明为signed char 或 unsigned。因此,定义有符号char类型变量,必须用signed显式声明。我曾读过一本书,其中有一句话:“signed关键字也是很宽宏大量,你也可以完全当它不存在,在缺省状态下,编译器默认数据位signed类型”,这句话便是有异议的,我们应该对自己所用的CPU构架以及编译器熟练掌握。

2.赋初值的全局变量和静态变量,初值被放在什么地方?

unsigned int g_unRunFlag=0xA5;
static unsigned int s_unCountFlag=0x5A;

这两行代码中,全局变量和静态变量在定义时被赋了初值,MDK编译环境下,你知道这个初值保存在那里吗?

对于在程序中赋初值的全局变量和静态变量,程序编译后,MDK将这些初值放到Flash中,紧靠在可执行代码的后面。在程序进入main函数前,会运行一段库代码,将这部分数据拷贝至相应RAM位置。若是你不小心将这些位置的数据擦除掉,嘿嘿...反正我是碰到了。

PS:后来看ARM的链接器,才知道ARM映象文件各组成部分在存储系统中的地址有两种:一种是在映象文件位于存储器中时(也就是该映象文件开始运行之前,通俗的说就是下载到Flash中的二进制代码)的地址,称为加载地址;一种是在映象文件运行时(通俗的说就是给板子上电,开始运行Flash中的程序了)的地址,称为运行时地址。赋初值的全局变量和静态变量在程序还没运行的时候,初值是被放在Flash中的,这个时候他们的地址称为加载地址,当程序运行后,这些初值会从Flash中拷贝到RAM中,这时候就是运行时地址了。

3.最新的keil MDK(V4.54)在编辑界面中已经可以支持中文编码了,所以可以在编辑器中直接输入汉字和中文标点符号,再也不会显示乱码或者不显示了。虽然乱写汉字和中文标点在编译时依然会报错,但好歹能显示,也从侧面说明中国市场的崛起。开启方法见http://blog.csdn.net/zhzht19861011/article/details/7741928 不再贴了。

我还清楚的记得自己在大学刚开始用Keil C51那会,一次不小心在一行代码后面用了个中文分号,在当时这个中文分号是不被显示的,然后编译,编译器报错,我双击报错信息定位到报错的代码行,却怎么也检查不出来错误来,当时着急的心情现在想想还很好笑的,那个时候只能将错误代码行用双斜杠注释掉,才能看到那个中文分号。但从V4.54之后,就应该再不会遇到我当时的情况了。

4.不知道从什么版本开始,keil MDK的标题栏可以显示工程路径了,我是从V4.10直接升级到V4.54,V4.10的标题栏还是下图的这个样子:

如果你同一个工程有多个备份,你有同时打开了多个备份工程,要想识别出那个工程是那个备份,可是件不容易的事情,还好,keil更新较快.

5.这一条真伪未知,因为我搜索了很久都没有查证.

在一个论坛上看到的,Keil原来是一个人名,住在德国,最初的keil C51编译器就是他开发的.为人低调,话不多,但超级认真.当然,也超级厉害.

6.Stack分配到RAM的哪个地方?

keil MDK中,我们只需要定义各个模式下的堆栈大小,编译器会自动在RAM的空闲区域选择一块合适的地方来分配给我们定义的堆栈,这个地方位于RAM的那个地方呢?通过查看编译列表文件,原来MDK将堆栈放到程序使用到的RAM空间的后面,比如你的RAM空间从0x4000 0000开始,你的程序用掉了0x200字节RAM,那么堆栈空间就从0x4000 0200处开始。具体的RAM分配,其实你可以从编译后生成的列表文件“工程名.map”文件中查看。

7.有多少RAM会被初始化?

大家可能都已经知道,在进入main()函数之前,MDK会把未初始化的RAM给清零的(在程序中自己定义变量初值的见第二条),但MDK会不会把所有RAM都初始化呢?答案是否定的,MDK只是把你的程序用到的RAM以及堆栈RAM给初始化,其它RAM的内容是不管的。如果你要使用绝对地址访问MDK未初始化的RAM,那就要小心翼翼的了,因为这些RAM的内容很可能是随机的,每次上电都不同。至少,NXP的LPC2000系列就是这样。

8.还是一个新版本的变化,还是关于版本V4.10和V4.54

V4.10版本,只要你重新打开工程,点击"Build target files"(就这个图标:),编译器就会将所有文件都编译一次,不管你的文件在这之前有没改动.但V4.54就不一样了,再次打开文件,点击"Build target files"它会只编译改过的文件的.早该这么做了,每次打开工程都要编译个十几秒钟,着实等的难受.

9.好个一丝不苟的编译器

这是个十分奇葩的问题,碰巧被我遇到了,我承认是我代码写的不够规范,但正是这个不规范的代码,才得以发现这个奇葩的事件。实在忍不住用了两个奇葩来形容。把过程简化一下,如下所述:

假如你的工程至少有两个.c文件,其中一个为timer.c,里面有个定时器中断程序,每10ms中断一次,定义一个变量来统计定时器中断次数:

unsigned int unIdleCount;

还有一个timer.h文件,里面是一些timer.c模块的封装,其中变量unIdleCount就被封装在里面:

extern unsigned int unIdleCount;

在main.c函数中,包含timer.h文件,并利用定时器变量unIdleCount来精确延时2秒,代码如下:

unIdleCount=0;

while(unIdleCount!=200);   //延时2S钟

keil MDK V5.54下编译,默认优化级别,编译后下载到硬件平台。你会发现,代码在

while(unIdleCount!=200);

处陷入了死循环。反汇编,代码如下:

   122:     unIdleCount=0;
   123:
0x00002E10  E59F11D4  LDR       R1,[PC,#0x01D4]
0x00002E14  E3A05000  MOV       R5,#key1(0x00000000)
0x00002E18  E1A00005  MOV       R0,R5
0x00002E1C  E5815000  STR       R5,[R1]
   124:     while(unIdleCount!=200);   //延时2S钟
   125:
0x00002E20  E35000C8  CMP       R0,#0x000000C8
0x00002E24  1AFFFFFD  BNE       0x00002E20

重点看最后两句汇编代码,寄存器R0是当前变量unIdleCount的值,汇编指令CMP为比较指令,如果R0中的内容与0xC8不等,则循环。但是这里并没有更新寄存器R0的代码,也就是说变量unIdleCount的值虽然在变化,但跟0xC8一直比较的却是内容不变的R0。因为之前变量unIdleCount被清零,所以R0的内容也是0,永远不等于0xC8,永远不会跳出循环。

看到这里,也许你已经笑翻了:你这个小白,这很明显是没用volatile修饰变量unIdleCount造成的!!!不错,比起从RAM中读写数据,ARM或其它硬件从寄存器读取数据要快的多的多的多...因此编译器会“自作主张”的将某些变量读到寄存器中,再次运算时也优先从寄存器中读取,上面的例子就是这样。解决这样的方法是用关键字volatile修饰你不想让编译器优化的变量,明白的告诉编译器:你不准优化我,每次使用我你都要本本分分的从RAM中读取或写入RAM。

所以先不要笑,我是不会犯这种错误的,之所以从这里说起,是为了照顾下还不知道volatile关键字的。。。

其实在timer.c中我是这样定义统计定时器中断次数变量的:

unsigned int volatile unIdleCount;

但是,在timer.h中,我确偷了个懒,声明这个变量的代码如下:

extern unsigned int unIdleCount;

没有使用关键字volatile,在keil MDK V5.54下编译,默认优化级别,然后查看代码的反汇编,如下所示:

   122:     unIdleCount=0;
   123:
0x00002E10  E59F11D4  LDR       R1,[PC,#0x01D4]
0x00002E14  E3A05000  MOV       R5,#key1(0x00000000)
0x00002E18  E1A00005  MOV       R0,R5
0x00002E1C  E5815000  STR       R5,[R1]
   124:     while(unIdleCount!=200);   //延时2S钟
   125:
0x00002E20  E35000C8  CMP       R0,#0x000000C8
0x00002E24  1AFFFFFD  BNE       0x00002E20

可以看出,这个反汇编代码居然和没加volatile关键字的时候一模一样!!代码还是会在while出陷入死循环。

现在,应该知道我要表达的意思了吧,如果引用的变量声明中没有使用volatile关键字修饰,即便定义这个变量的时候使用了volatile关键字修饰,MDK编译器照样优化掉它!

将timer.h中的声明更改为:

extern unsigned int volatile unIdleCount;

同样环境下编译,查看反汇编代码,如下所示:

   122:     unIdleCount=0;
   123:
0x00002E10  E59F01D4  LDR       R0,[PC,#0x01D4]
0x00002E14  E3A05000  MOV       R5,#key1(0x00000000)
0x00002E18  E5805000  STR       R5,[R0]
   124:     while(unIdleCount!=200);   //延时2S钟
   125:
0x00002E1C  E5901000  LDR       R1,[R0]
0x00002E20  E35100C8  CMP       R1,#0x000000C8
0x00002E24  1AFFFFFC  BNE       0x00002E1C

看最后三句汇编代码,发现多了一个载入汇编指令LDR,这个指令在每次循环中都将变量unIdleCount从RAM中读出到寄存器R1中,然后R1的值再和0xC8比较。这才是符合逻辑的需要的代码。

其实如果好好看看编译原理的书,是不会犯这么低级的错误的,编译器是分文件编译,然后链接,文件A使用了文件B中定义的变量,在编译的时候,文件A是完全不知道文件B里面有什么东西的,只能通过文件B的接口文件(.h文件)来获得使用变量的属性.

以这个为例子,着重说明下关键字volatile,同时也要掌握编译原理的知识,用好手中的工具.

10.关于float类型

在keil中,在不选择"Optimize for time"编译选项时,局部float变量占用8个字节(编译器默认自动扩展成double类型),如果你从Flash中读取一个float类型常量并放在局部float型变量中时,有可能发生意想不到的错误:Cortex-M3中可能会出现硬fault.因为字节对齐问题.

但有趣的是,一旦你使用"Optimize for time"编译选项,局部float变量只会占用4个字节.

11.默认情况下,从按下复位到执行你编写的C代码main函数,keil mdk做了些什么?

硬件复位后,第一步是执行复位处理程序,这个程序的入口在启动代码里(默认),摘录一段cortex-m3的复位处理入口代码:

Reset_Handler   PROC        ;PROC等同于FUNCTION,表示一个函数的开始,与ENDP相对?
                EXPORT  Reset_Handler             [WEAK]
        IMPORT  SystemInit
                IMPORT  __main
        LDR     R0, =SystemInit
                BLX     R0
                LDR     R0, =__main
                BX      R0
                ENDP

这里SystemInit函数是我自己用C代码写的硬件底层时钟初始化代码,这个可不算是keil mdk给代劳的.初始化堆栈指针、执行完用户定义的底层初始化代码后,发现接下来的代码是调用了__main函数,这里之所以有__main函数,是因为在C代码中定义了main函数,函数标签 main() 具有特殊含义。main() 函数的存在强制链接器链接到 __main 和 __rt_entry 中的初始化代码。

其中,__main函数执行代码和数据复制、解压缩以及  ZI 数据的零初始化。解释一下,C代码中,已经赋值的全局变量被放在RW属性的输入节中,这些变量的初值被keil mdk压缩后放到ROM或Flash中(RO属性输入节)。什么是赋值的全局变量呢?如果你在代码中这样定义一个全局变量:int nTimerCount=20;变量nTimerCount就是已经赋值的变量,如果是这样定义:int nTimerCount;变量nTimerCount就是一个非赋值的变量,keil默认将它放到属性为ZI的输入节。为什么要压缩呢?这是因为如果赋值变量较多,会占用较多的Flash存储空间,keil  默认用自己的压缩算法。这个“解压缩”就是将存放在RO输入区(一般为ROM或Flash)的变量初值,按照一定算法解压缩后,拷贝到相应RAM区。ZI数据清零是指将ZI区的变量所在的RAM区清零。使用 UNINIT 属性对执行区进行标记可避免 __main 对该区域中的  ZI 数据进行零初始化。这句话很重要,比如我有一些变量,保存一些重要信息,不希望复位后就被清零,这时就可以用分散加载文件定义一块UNINIT属性的区,将不希望零初始化的变量定义到这个区即可。

12.关于新版本V4.70

期盼已久的功能终于在V4.7实现了!!IDE升级到了µVision V4.70.00 ,增加了代码和参数自动补全以及动态语法检验,增加了两个性能分析命令。J-LINK驱动更新到V4.62,编译器版本更新到5.03.其中我是最喜欢的是代码和参数自动补全以及动态语法检验。刚回答了一个百度知道提问,提问者问到V4.70a为什么不能自动代码和参数补全,这里也说一下,这并不是4.70a的问题,4.70a相对于4.70只是修正了"linking in MDK-ARM user guides"问题,使能自动补全的功能要设置一下:点击Edit-Configuration...,在打开的对话框中选中Text Completion标签栏,在此页面中选中symbols after复选框即可完全开启(安装后默认并没有选中这个框).补充,你的系统至少要有vc++2010运行库,才能看到这个设置。
另外V4.70自带的动态语法校验我也非常喜欢,在输入的时候就可以检查出我输入的变量名称,标点,函数名等等对不对了.推荐升级测试一下。
时间: 2024-10-08 09:48:29

有趣的keil MDK细节(转)的相关文章

Keil MDK下如何设置非零初始化变量(转)

源:Keil MDK下如何设置非零初始化变量 一些工控产品,当系统复位后(非上电复位),可能要求保持住复位前RAM中的数据,用来快速恢复现场,或者不至于因瞬间复位而重启现场设备.而keil mdk在默认情况下,任何形式的复位都会将RAM区的非初始化变量数据清零.如何设置非初始化数据变量不被零初始化,这是本篇文章所要探讨的. 在给出方法之前,先来了解一下代码和数据的存放规则.属性,以及复位后为何默认非初始化变量所在RAM都被初始化为零了呢. 什么是初始化数据变量,什么又是非初始化数据变量?(因为我

Keil MDK与h-jtag联调

keil MDK也是可以借助h-jtag进行单步调试,写出来与大家一起分享一下. keil MDK编译器使用V4.01版本,下载地址: http://www.embedinfo.com/down-list.asp?id=714   (需要注册一下) h-jtag使用V1.0版本(请注意,一定要用V1.0或者以上版本才可以与mdk兼容),下载地址 http://www.hjtag.com/download/H-JTAG%20V1.0%20Preview.zip 分别安装MDK与h-jtag 两个软

Keil MDK从未有过的详细使用讲解

转自博客:http://blog.csdn.net/zhzht19861011/article/details/5846510 这博主关于MDK 的使用的文章,写的得TM的好  TM的实用! 真心收藏! 熟悉Keil C 51的朋友对于Keil MDK上手应该比较容易,毕竟界面是很像的.但ARM内核毕竟不同于51内核,因此无论在设置上还是在编程思想上,都需要下番功夫研究的.本文以MDK V4.03为例,详细的写一下MDK的设置.界面.工具.可能会有些杂乱,但我想所涉及的东西都是最常用的:可能不是

Keil MDK忽略某一警告

使用Keil MDK调试程序的时候,没有习惯在每个文件的末尾增加一个空白行,结果文件一多,编译时产生的警告就一大堆,排错都得用滚轮滚好久,就一个空白行还得出警告,烦死了,烦死了,烦死了!实在受不了了,查阅了mdk的帮助文档,发现可以通过增加控制选项的方式,屏蔽某一固定码的警告.具体方式如下: 点击编译,查看警告编码:我这边产生的空白行警告是1,所以,打开工程配置菜单,点击C/C++选项卡,在Misc Controls里面输入: --diag_suppress=1 其中的1就是警告编码,然后点击确

Keil MDK编译器的数据类型定义

1 #include "usart.h" 2 int main() 3 { 4 USART1_Config(); //串口初始化,以下信息通过串口打印 5 printf("char: %d\r\n",sizeof(char)); 6 printf("unsigned char: %d\r\n",sizeof(unsigned char)); 7 printf("short: %d\r\n",sizeof(short)); 8

KEIL MDK 查看代码量、RAM使用情况--RO-data、RW-data、ZI-data的解释(转)

源:KEIL MDK 查看代码量.RAM使用情况--RO-data.RW-data.ZI-data的解释 KEIL RVMDK编译后的信息 Program Size: Code=86496 RO-data=9064 RW-data=1452 ZI-data=16116 Code是代码占用的空间; RO-data是 Read Only 只读常量的大小,如const型; RW-data是(Read Write) 初始化了的可读写变量的大小; ZI-data是(Zero Initialize) 没有初

转:Keil MDK从未有过的详细使用讲解

来自:http://blog.csdn.net/zhzht19861011/article/details/5846510 熟悉Keil C 51的朋友对于Keil MDK上手应该比较容易,毕竟界面是很像的.但ARM内核毕竟不同于51内核,因此无论在设置上还是在编程思想上,都需要下番功夫研究的.本文以MDK V4.03为例,详细的写一下MDK的设置.界面.工具.可能会有些杂乱,但我想所涉及的东西都是最常用的:可能不是那么的严谨清晰,我想谁也没把我期望成专家!哈,有问题欢迎留言.正式开始.   首

KEIL MDK 5.12帮你快速建工程模板的技巧

KEIL 5帮你快速建工程模板的技巧 本人使用keil mdk 5.12有一段时间了,发现keil mdk 5.12里面驱动库比较方便.这个新功能可以节省我们的时间,也可以让初学者能尽快上手和掌握这个芯片.大家以后既就可以很方便的建自己的工程模板了,再也不会为了找芯片的驱动库而烦恼. 今天外我来教大家来怎么使用这个建工程的利器吧. 首先是新建工程吧,如下图步骤: 因为我用的是stm32f401 Necleo,所以芯片型号应该要选择stm32f401te,选好芯片类型后,会弹出一个大的面板,如下图

keil mdk 菜单 “project” 崩溃问题解决

今天发现我的 Keil MDK 点击 Project 菜单立即崩溃. 简单粗暴的处理方法:重装keil ,但是依然点击 project 崩溃. 通过搜索发现解决办法:清除最近项目列表即可. 操作如下: 1.运行 REGEDIT 2.删除 计算机\HKEY_CURRENT_USER\Software\Keil\uVision5\Recent Projects 的全部内容, 一切OK ! 本人原创博客欢迎转载.引用,注明出处即可.