从一个小程序详解codewarrior for MC68HC908GP32CP C语言指针

类别:软件工程 点击:0 评论:0 推荐:

#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