#include <hidef.h>
#include <MC68HC908GP32.h>
/* 在codewarrior中建立了C语言的程序之后程序自动引入头文件MC68HC908GP32.h在这个头文件中定义了
typedef unsigned char byte;
typedef unsigned int word;
typedef unsigned long dword;
typedef unsigned long dlong[2];
如果足够用,建议使用byte而不是int,因为它只占用一个字节,在单片机中内存是稀缺资源,非常宝贵*/
byte a @ 0x0050; // 定义unsigned char放在50单元,这种定义方式只能用于
// 全局变量。
byte *p @ 0x0051; // 定义一个指针放在0x0051单元,指向一个unsigned char,
// 究竟指向什么地址还没有定义。注意!这里非常重要的是,
// 虽然byte(unsigned char)只有一个字节,但是这个指针却
// 是两个字节的,也就是说在0x0051和0x0052分别存放了这
// 个指针的高、低字节。
byte b @ 0x0060; // 定义一个变量放在60单元
#define c (*(volatile byte*)0x0058) // 另一种为变量指定地址的方式:宏定义
// 不要为这种复杂的写法感到困惑或者烦躁,
// 它也可以分解成几个很简单的部分来看:
// (volatile byte*)是C语言中的牵制类型转换
// 它的作用是把0x0058这个纯粹的十六进制数
// 转换成为一个地址,其中volatile并不是必要
// 的,它只是告诉编译器,这个值与外环境有关,
// 不要对它优化。这里也没有必要对编译器的具
// 体机制讨论太多[1]。接下来在外面又加了一
// 个*号,就表示取0x0058内存单元中的内容了。
// 经过这个宏定义之后,c就被可以做为一个普通
// 的变量来操作,所有出现c的地方编译的时候
// 都被替换成(*(volatile byte*)0x0058),外
// 面一层括号是为了保证里面的操作不会因为运
// 算符优先级或者其它不可预测的原因被改变而
// 无法得到预期的结果。
void main(void)
{
byte i; // 定义byte类型变脸i,我也不知道编译之后它会被放在哪个内存单元
byte *p2; // 有一个指针,指向一个byte类型的数
a=0x70; // a赋值0x70,还记得吗,a在50单元也就是MOV #$70,$50
c=0x30; // MOV #$30,$58
p=(byte*)0x60; // p指向60单元,60单元存放的是b,于是P就很自然的成了指向b的指针(p=&b;)
// 这里的(byte*)是不能省略的,这也是一个强制类型转换,它告诉编译器,
// 0x60再也不是一个纯粹的十六进制数了,它已经代表了一个内存单元。
p2=(byte*)a; // a中存放的数是0x70,现在把它强制转换成地址类型,p2->0x0070
for(i=0;i<0x10;++i) // 循环16次
{
*p=i+a; // 把i+0x70的值放到p指向的地址中,循环开始时,p->0x60,
// 以后每次增加1,直到p->0x70,循环结束后,就可以看到,
// 60到70单元中存放了70,71,……,7F。
*p2++=(byte)p++; // 把p指向的地址值保存在p2指向的地址中(从0x60开始),然后
// p增1用于下一次循环;p2增1用于保存下一次循环中p指向的
// 地址。循环结束后,可以很清楚地看到,从60单元到6F单元
// 保存的16次循环中指针p所有的值:60,61,……,6F。
// 同前面一样,这里的(byte)是必不可少的,p原来是指针,保存
// 的是地址值,现在要把地址值当作纯粹的十六进制数来赋值
// 给p2指向的单元,就必须强制类型转换。
*((byte*)0x0080)=(byte)p; // [2]这种写法其实我们一开始的时候就用到了。对!就是
// 跟定义宏 #define c (*(volatile byte*)0x0058) 时
// 候语法完全一样。这种写法可以完全不用理会是不是要
// 定义全局变量(使用@关键字的时候必须定义全局变量),
// 也可以避免使用#define进行宏定义这种有可能成为一切
// 不安全因素根源的写法,而在程序运行过程中,把变量
// 放置在任意允许放置的内存单元。这一点非常有用,尤
// 其是在需要看到当前某个变量的值的场合。
//__RESET_WATCHDOG(); // 暂时不理会看门狗。因为程序小小的循环16次还不至于
// 让它溢出复位。但是我把它写出来,因为后面有更玄妙
// 的问题。
}
*((byte*)0x0081)=(byte)p; // [3]循环结束,把P指向的地址值放到81单元中。哈哈,
// 用膝盖都可以想到,81单元中存放的值一定是70。
*((int*)0x0082)=(int)&i; // 快要结束了,现在我们能做的事情已经那么多了,于是
*((int*)0x0084)=(int)&p2; // 谁还能容忍存在未知的事物呢?上面我说我也不知道编译
// 器在编译之后会把变量i(或者是指针P2)放到哪里去,可
// 是如果你现在还只满足于不知道,那可太悲哀了:(。我
// 可不是个求知欲容易满足的人,我一定要弄清楚。于是
// 这种强制类型转换的机制又一次帮助了我们。我不解释
// 这两句了,因为它们现在已经变得那样明了。唯一说明
// 一点,这里用int而没有用byte是因为我可不敢保证i和
// p2的地址一个字节装得下。
}
/* 好了,循环结束,程序运行完了,下面要提出那个玄妙的问题了。有没有发现[2]和[3]两处做了同一件事情,都是把p指向的地址保存起来。为了方便比较,我选择两个相邻的地址80和81。还有一个地方也保存了p指向的地址,就是51单元(还记得byte *p @ 0x0051;吗?)。我强调过,byte是一个字节,可是byte*默认为两个字节,而我在[2]和[3]两处把两个字节的p用(byte)强制类型转换成了一个字节的十六进制数,会产生错误吗?这里不会。很简单,因为这里的p本身就只有一个字节,强制类型转换之后,80和81单元都保存的是p的低字节,这已经足够了。0x51,0x80,0x81都保存了p指向的地址的值,有什么不一样呢?0x51是p定义时候的位置,可以算是它的hometown了,0x81存放的是循环结束后p指向的地址,也就是0x70,而0x80呢?它存放每次循环末尾的p的值,与51单元和81单元不同的是,每次循环的时候,他都是会变的,变化过程就是循环结束后0x60到0x6F地址中存放的那些值。由此可以推断,在程序最后一次循环结束后,0x80与0x81单元的值应该是一致的,看看你的内存,一致吗?0x51和0x80是一致的,而和0x81呢?呵呵,如果我没猜错的话,51单元和80单元的值都是64,而81单元跟我们之前所确定的一样,是千真万确的70。为什么呢?发现问题本身比解决问题有更大的意义。为了解决这个问题,我们只有单步运行了。在main()函数的入口处设置一个端点,然后点连续运行,让程序在初始化的那些部分执行完后停下来,在main()入口处开始单步运行。一步,两步,三步,...,一直到十六次循环全都执行完,惊奇地发现51单元和80单元确实是70,这下更困惑了,为什么单步运行就能得到预期的结果,连续运行就不行呢?别急,故事还没有结束,继续单步运行,咦,程序跳到了什么地方?呵呵,欢迎来到天涯海角^_^。这是程序其实的地方,也就是刚才我们设置断点之后连续运行跳过的地方,程序回到了这里,还能说明什么——这个总程序是在永远循环执行的,他不会停止,一遍完成,再来一遍。哎,探索真理的路程真是艰辛而又充满趣味:)。好了,现在总算可以解答那个玄妙的问题了:为什么51单元、80单元中的指针地址会和81单元中不一样。81单元中的值是for循环结束后指针p的最后地址(6F)加1(70),这一点毋庸置疑,而51单元和80单元在总程序第二遍、第三遍、……不断的重复执行过程中又被改变了,在这期间,81单元虽然也是每次都被重写,可是都是被同一个值70重写,所以不会有变化。总程序不会这样一遍再一遍无穷无尽的执行下去的,记得我们在 __RESET_WATCHDOG(); 这一句话的注释里买了个关子,现在是揭示谜底的时候了,因为没有喂看门狗,在程序这样一遍遍反复执行的时候,总会在某一处使得看门狗计数器溢出复位,这样整个程序就停了下来,可是停的时候for循环并不一定执行完成了16次,它可能是在执行任何一次循环的时候看门狗复位了,这个时候51单元和80单元的值就成了当前指针p指向的地址,这里就是0x64,而此时81单元还是上一次for循环结束后的值70,当然不会一样。问题解决了,但是并不完整,如果我们禁止掉看门狗会怎么样呢?去掉__RESET_WATCHDOG(); 前面的引用,在执行程序,这个时候看到CPU Cycles不断的增加,程序一直在运行,打开内存窗口,会开到51单元和80单元的值在不断的变化(这里有个小技巧,全屏内存窗口以后,点击右边的滚动条,窗口里面的值就会不断刷新了),当然,你也可以人为让程序停在某个地方,而这个时候51和80单元的值是不定的,两者可能相等,也可能相差1,而81单元则一定是70。到这里问题总算得到了圆满的解决。*/
/* 总结一下要点:
1. byte默认为一个字节,但是byte*默认为两个字节。
2. codewarrior中的c语言程序是不断的循环运行的,即使程序本身没有循环,在一遍执行完成后它也会自动从头再开始,直到看门狗计数器溢出复位(如果看门狗没被有关掉的话)。
3. 三种方法把一个值放置到指定内存单元:
1)宏定义。例如:
#define value1 (*(volatile unsigned char*)0x0040) //定义变量value1在40单元[4]。
2)使用@关键字,这时变量只能是全局的。例如:
volatile unsigned char value2 @ 0x0041;
3)使用强制类型转换把一个十六进制数换成地址值用来存放一个值,例如:
*((unsigned char*)0x0042)=value3; //当然,value3应该是之前定义好的、并且已经被赋值的。
发挥一下想象力,我们甚至可以这么写:
for(i=0;i<0x10;++i)
*((unsigned char*)0x0043+i)=value4[i]; // 看出来了吗?我们把一个数组中的每一个值分别放到
// 43开始的每一个内存单元;这里数组value4[]也应该
// 是之前就有了的。
*/
//----------------------------------------------------------------------------------------------------------
/* [1]其实这里(volatile byte*)的作用类似C++中的reinterpret_cast,也可具体写作
byte* pointer=new (reinterpret_cast<void*>(0x0058)) byte
不同的是这里的pointer是指针,而程序中定义的c仅仅是一个byte类型的变量。可以参考The C++ programming language(special edition
10.4.11)
[4]宏定义并没有真正的定义变量,它只是一个编译的命令。它告诉编译器,在编译的时候把后面程序中所有的value都替换成(*(volatile unsigned char*)0x0040)。
*/
本文地址:http://com.8s8s.com/it/it32897.htm