×
单片机 > 单片机程序设计 > 详情

C51中关于指针的种种用法

发布时间:2020-06-08 发布时间:
|
#i nclude

f(){}
f1(){}
f2(){}

main()
{
    {
        int x;
        int *px;

        //下面这些表示虽然很烦,但是生成的代码却及其简洁:(黑体部分实际使用过)

        //将 xdata 型指针 0x4000 赋给 px
        px=(int xdata *)0x4000;

        //表示从 xdata 0x4000处取一个 char 给x
        x=*((char xdata *)0x4000); (可以将0X4000处,改成一个整形变量,方便进行操作。)

        或者*((char xdata *)0x4000)=X;//表示给存在xdata中,地址为0x4000的空间赋值。

        // 表示从 code 0x4000处取一个 word 作为 xdata 型的指针 给
px
        px=*((int xdata * xdata *)0x4000);

        //表示从 code 0x4000处取一个 word 作为 xdata 型的指针,
        //再把这个指针指向的char数据赋给x
        x=**((char xdata * code *)0x4000);

        //表示把函数f()入口地址当作xdata型指针,再把指向的xdata
        //中的int型数据作为code型指针,把指向的code字节
        //赋给x(晕,这样有意义吗?)
        x=**(int code * xdata *)f;

        //把f()入口地址处的ROM中两个code型字节,
        //赋给堆栈指针SP指向的字节(想干什么?编操作系统?)
        *(unsigned int idata *)SP=*(unsigned int code *)&f;

        //表示把f()入口地址处的ROM中两个code型字节,
        //作为一个xdata指针寻址,
        //把指向的数据作为pdata指针寻址,
        //再把把指向的数据作为idata指针寻址,
        //把该地址处的一个字节赋给x (我靠,累死了)
        x= ****(unsigned int data * idata * pdata * xdata * code 
*)&f;
        
        //总之,一个括号里面外面的"*"一样多就表示指向的是数据。
    }

    {
        //数组函数
        code void (*ArrFn[])(void) =
        {    &f1,
            &f2,
        };
        //可以像引用数组一样调用函数啦:
        (*ArrFn[0])();
        (*ArrFn[1])();
    }

    {
        //这样将使函数调用0000H处:
        void (*reset) (void);
        reset=0x0;
        (*reset)();
        reset();

        //或者直接这样,仅仅生成一条指令LCALL 1234H
        ((void (code *)(void))0x1234)(); 
    }

    {
        //这样可以调用RETI指令:
        #define  INT_NUM  30    //空闲中断号
        ((void (code *)(void))(INT_NUM*8+3))();
        //当然需要在外面声明   int_rpt()interrupt INT_NUM {}
    }
    {
        //这样调用RETI指令太变态:
        code unsigned char ret_i=0x32;
        ((void (code *)(void))(&ret_i))();
    }
}
int_rpt()interrupt INT_NUM {}

指针类型和存储区的关系详解

一、存储类型与存储区关系

    data     --->    可寻址片内ram
    bdata    --->    可位寻址的片内ram
    idata    --->    可寻址片内ram,允许访问全部内部ram
    pdata    --->    分页寻址片外ram (MOVX @R0) (256 BYTE/页)
    xdata    --->    可寻址片外ram (64k 地址范围)
    code     --->    程序存储区 (64k 地址范围),对应MOVC @DPTR

二、指针类型和存储区的关系

    对变量进行声明时可以指定变量的存储类型如:
    uchar data x和data uchar x相等价都是在内ram区分配一个字节的变量。

    同样对于指针变量的声明,因涉及到指针变量本身的存储位置和指针所指向的存储区位置不同而进行相应的存储区类型关键字的
使用如:

    uchar xdata * data pstr

    是指在内ram区分配一个指针变量("*"号后的data关键字的作用),而且这个指针本身指向xdata区("*"前xdata关键字的作用),
可能初学C51时有点不好懂也不好记。没关系,我们马上就可以看到对应“*”前后不同的关键字的使用在编译时出现什么情况。

    ......
    uchar xdata tmp[10];    //在外ram区开辟10个字节的内存空间,地址是外ram的0x0000-0x0009
    ......

    第1种情况:

    uchar data * data pstr;
    pstr=tmp;

    首先要提醒大家这样的代码是有bug的, 他不能通过这种方式正确的访问到tmp空间。 为什么?我们把编译后看到下面的汇编
代码:

    MOV 0x08,#tmp(0x00)        ;0x08是指针pstr的存储地址

    看到了吗!本来访问外ram需要2 byte来寻址64k空间,但因为使用data关键字(在"*"号前的那个),所以按KeilC编译环境来说
就把他编译成指向内ram的指针变量了,这也是初学C51的朋友们不理解各个存储类型的关键字定义而造成的bug。特别是当工程中的
默认的存储区类为large时,又把 tmp[10] 声明为uchar tmp[10] 时,这样的bug是很隐秘的不容易被发现。

    第2种情况:

    uchar xdata * data pstr;
    pstr = tmp;

    这种情况是没问题的,这样的使用方法是指在内ram分配一个指针变量("*"号后的data关键字的作用),而且这个指针本身指向
xdata 区("*"前xdata关键字的作用)。编译后的汇编代码如下。

    MOV 0x08,#tmp(0x00)        ;0x08和0x09是在内ram区分配的pstr指针变量地址空间
    MOV 0x09,#tmp(0x00)

    这种情况应该是在这里所有介绍各种情况中效率最高的访问外ram的方法了,请大家记住他。

    第3种情况:

    uchar xdata * xdata pstr;
    pstr=tmp;

    这中情况也是对的,但效率不如第2种情况。编译后的汇编代码如下。

    MOV DPTR, #0x000A        ;0x000A,0x000B是在外ram区分配的pstr指针变量地址空间
    MOV A, #tmp(0x00)
    MOV @DPTR, A
    INC DPTR
    MOV A, #tmp(0x00)
    MOVX @DPTR, A

    这种方式一般用在内ram资源相对紧张而且对效率要求不高的项目中。

    第4种情况:

    uchar data * xdata pstr;
    pstr=tmp;

    如果详细看了第1种情况的读者发现这种写法和第1种很相似,是的,同第1 种情况一样这样也是有bug的,但是这次是把pstr分
配到了外ram区了。编译后的汇编代码如下。

    MOV DPTR, #0x000A        ;0x000A是在外ram区分配的pstr指针变量的地址空间
    MOV A, #tmp(0x00)
    MOVX @DPTR, A

    第5种情况:

    uchar * data pstr;
    pstr=tmp;

    大家注意到"*"前的关键字声明没有了,是的这样会发生什么事呢?下面这么写呢!对了用齐豫的一首老歌名来说就是 “请跟我
来”,请跟我来看看编译后的汇编代码,有人问这不是在讲C51吗? 为什么还要给我们看汇编代码。C51要想用好就要尽可能提升C51
编译后的效率,看看编译后的汇编会帮助大家尽快成为生产高效C51代码的高手的。还是看代码吧!

    MOV 0x08, #0X01            ;0x08-0x0A是在内ram区分配的pstr指针变量的地址空间
    MOV 0x09, #tmp(0x00)
    MOV 0x0A, #tmp(0x00)

    注意:这是新介绍给大家的,大家会疑问为什么在前面的几种情况的pstr指针变量都用2 byte空间而到这里就用3 byte空间了
呢?这是KeilC的一个系统内部处理,在KeilC中一个指针变量最多占用 3 byte空间,对于没有声明指针指向存储空间类型的指针,
系统编译代码时都强制加载一个字节的指针类型分辩值。具体的对应关系可以参考KeilC的help中C51 User's Guide。

    第6种情况:

    uchar * pstr;
    pstr=tmp;

    这是最直接最简单的指针变量声明,但他的效率也最低。还是那句话,大家一起说好吗!编译后的汇编代码如下。

    MOV DPTR, #0x000A        ;0x000A-0x000C是在外ram区分配的pstr指针变量地址空间
    MOV A, #0x01
    MOV @DPTR, A
    INC DPTR
    MOV DPTR, #0x000A
    MOV A, #tmp(0x00)
    MOV @DPTR, A
    INC DPTR
    MOV A, #tmp(0x00)
    MOVX @DPTR, A

    这种情况很类似第5种和第3种情况的组合,既把pstr分配在外ram空间了又增加了指针类型的分辨值。

    小结一下:大家看到了以上的6种情况,其中效率最高的是第2种情况,既可以正确访问ram区又节约了代码,效率最差的是第 6
种,但不是说大家只使用第2种方式就可以了,还要因情况而定,一般说来应用51系列的系统架构的内部ram资源都很紧张,最好大家
在定义函数内部或程序段内部的局部变量使用内ram,而尽量不要把全局变量声明为内ram区中。所以对于全局指针变量我建议使用第
3 种情况,而对于局部的指针变量使用第2种方式。

与指针有关的各种说明和意义见下表。
int *p;     p为指向整型量的指针变量;

int xdata *p;   存在外部数据RAM;

int data *p;    存在内部数据RAM; 

int code *p;   存在程序代码空间;

int data *xdata p; 外部RAM指针,指向内部RAM整形数据

int xdata *data p; 内部RAM指针,指向外部RAM整形数据

       
int *p[n];   p为指针数组,由n个指向整型量的指针元素组成。


int (*p)[n];   p为指向整型二维数组的指针变量,二维数组的列数为n


int *p()    p为返回指针值的函数,该指针指向整型量


int (*p)()   p为指向函数的指针,该函数返回整型量


int **p     p为一个指向另一指针的指针变量,该指针指向一个整型量。

{ int x;
  int *px;

        //下面这些表示虽然很烦,但是生成的代码却及其简洁:

        //将 xdata 型指针 0x4000 赋给 px
        px=(int xdata *)0x4000;

        //表示从 xdata 0x4000处取一个 char 给x
        x=*((char xdata *)0x4000);

}

阅读组合说明符的规则是“从里向外”。
从标识符开始,先看它右边有无方括号或园括号,如有则先作出解释,再看左边有无*号。 如果在任何时候遇到了闭括号,则在继续之前必须用相同的规则处理括号内的内容。例如: 
int*(*(*a)())[10]
↑ ↑↑↑↑↑↑
 7   6 4  2  1 3  5
上面给出了由内向外的阅读顺序,下面来解释它:
(1)标识符a被说明为;
(2) 一个指针变量,它指向;
(3)一个函数,它返回;
(4)一个指针,该指针指向;
(5)一个有10个元素的数组,其类型为;
(6) 指针型,它指向;
(7)int型数据。
因此a是一个函数指针变量,该函数返回的一个指针值又指向一个指针数组,该指针数组的元素指向整型量。


『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
TQ210天嵌开发板S5PV210 LED闪烁程序C语言代码记录