MDK使用开发中的一些技巧整理

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

在 MDK 中,默认情况下,char 类型的数据项是无符号的,所以它的取值范围是 0~255。它们可以显式地声明为 signed charunsigned。因此,定义有符号 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,永远不等于 0xC

8,自然一直死循环。

这个问题在 MDK V4.54 是不存在的,V4.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 E59F11D4 LDR R1,[PC,#0x01D4]
0x00002E24 E59F11D4 LDR R0,[R1]
0x00002E28 E35000C8 CMP R0,#0x000000C8
0x00002E2C 1AFFFFFD BNE 0x00002E20

我们可以看到,在比较前又多了一个 LDR 指令,这条指令重新从 RAM 中读取了 unIdleCount 的值,这才是正常的编译优化。想想无论是嵌入式开发也好,还是 PC 开发,也好,开发者都应当做到一丝不苟。因为在进行代码优化时,编译器会更偏向于选择快速的循环,而忽视数据的状态更新。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

2401_87496566

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值