EDN首页   博客首页

0

关于投票
深入了解指向指针的指针

编程的时候,看到下面的一个函数:

eMBErrorCode
eMBASCIIReceive( UCHAR * pucRcvAddress, UCHAR ** pucFrame, USHORT * pusLength )
{
    eMBErrorCode    eStatus = MB_ENOERR;

    ENTER_CRITICAL_SECTION(  );
    assert( usRcvBufferPos < MB_SER_PDU_SIZE_MAX );
    
    /* Length and CRC check */
    if( ( usRcvBufferPos >= MB_SER_PDU_SIZE_MIN )
        && ( prvucMBLRC( ( UCHAR * ) ucASCIIBuf, usRcvBufferPos ) == 0 ) )
    {
        /* Save the address field. All frames are passed to the upper layed
         * and the decision if a frame is used is done there.
         */
        *pucRcvAddress = ucASCIIBuf[MB_SER_PDU_ADDR_OFF];

        /* Total length of Modbus-PDU is Modbus-Serial-Line-PDU minus
         * size of address field and CRC checksum.
         */
        *pusLength = ( USHORT )( usRcvBufferPos - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_LRC );

        /* Return the start of the Modbus PDU to the caller. */
        *pucFrame = ( UCHAR * ) & ucASCIIBuf[MB_SER_PDU_PDU_OFF];
    }
    else
    {
        eStatus = MB_EIO;
    }
    EXIT_CRITICAL_SECTION(  );
    return eStatus;
}

当看到UCHAR ** pucFrame的时候就优点晕了。不就是传出一个地址吗?干吗还要用指向指针的指针?于是,顺手修改了程序,将那个形参变成了UCHAR * pucFrame,具体的函数也变成了下面的样子:

eMBErrorCode
eMBASCIIReceive( UCHAR * pucRcvAddress, UCHAR * pucFrame, USHORT * pusLength )
{
    eMBErrorCode    eStatus = MB_ENOERR;

    ENTER_CRITICAL_SECTION(  );
    assert( usRcvBufferPos < MB_SER_PDU_SIZE_MAX );
    
    /* Length and CRC check */
    if( ( usRcvBufferPos >= MB_SER_PDU_SIZE_MIN )
        && ( prvucMBLRC( ( UCHAR * ) ucASCIIBuf, usRcvBufferPos ) == 0 ) )
    {
        /* Save the address field. All frames are passed to the upper layed
         * and the decision if a frame is used is done there.
         */
        *pucRcvAddress = ucASCIIBuf[MB_SER_PDU_ADDR_OFF];

        /* Total length of Modbus-PDU is Modbus-Serial-Line-PDU minus
         * size of address field and CRC checksum.
         */
        *pusLength = ( USHORT )( usRcvBufferPos - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_LRC );

        /* Return the start of the Modbus PDU to the caller. */
        pucFrame = ( UCHAR * ) & ucASCIIBuf[MB_SER_PDU_PDU_OFF];
    }
    else
    {
        eStatus = MB_EIO;
    }
    EXIT_CRITICAL_SECTION(  );
    return eStatus;
}

    编译后运行,却得不到自己想要的结果。只得回来再分析这个函数。首先搜索到CSDN上的一篇文章;http://dev.csdn.net/author/kgdiwss/477390ddb16b44faa06b6c7014908469.html,里面又好好地对指针做了分析。我下面就把自己的理解再写一遍,以加深印象。

1,指针在编译的时候会变成什么?

    如果我们如下声明变量:

    char a;

    shourt int i;

   short int *pi;

    那么编译的时候,编译器会在内存空间的某处为上面的变量开辟存储空间,下面是一个可能的示意图:

    内存地址 1      2       3        4       5        6       7       8        9          10

------------------------------

                 |int a |short int i    |short int *pi  |

    如上所示,int a 占用一个字节,short int i 占用2个字节, short int *pi 占用2个字节。当我们做了如下的赋值的时候:

    i = 88;

    pi = &a;

    内存映象会得到改变:

                 |int a   |     88        |      2        |

    从上面可以看到,pi是个指针,他的值是2,是变量 i 的内存的起始地址。当我们对 *pi 进行操作的时候,其实就是对变量i 进行操作。如 *pi = 66; ,则等价 i = 66;。下面我们再分析指向指针的指针。指针变量本身跟其他变量一样也是在某个内存地址中存储的,我们也可以让一个指针指向这个地址(指针)。看如下代码:

    short int  **ppi;

    ppi = &pi;

    首先声明了一个指向指针的指针变量ppi,这个ppi是用来存储一个short int *类型指针变量的地址的。第二句的意思是pi的地址赋值给ppi,就是将地址值4给ppi,如下图表示:

                 |int a   | short int i  |short int *pi|short int **ppi |

                 |int a   |     88        |      2          |            4         |

    这样,ppi的值为4,就是pi在内存中的起始地址

    *ppi的值为2,是pi的值

    **ppi的值是88,是i的值,也是*pi的值

    有了上面的分析,我们在看上面那个错误的函数:

eMBErrorCode
eMBASCIIReceive( UCHAR * pucRcvAddress, UCHAR * pucFrame, USHORT * pusLength )
{
    eMBErrorCode    eStatus = MB_ENOERR;

    ENTER_CRITICAL_SECTION(  );
    assert( usRcvBufferPos < MB_SER_PDU_SIZE_MAX );
    
    /* Length and CRC check */
    if( ( usRcvBufferPos >= MB_SER_PDU_SIZE_MIN )
        && ( prvucMBLRC( ( UCHAR * ) ucASCIIBuf, usRcvBufferPos ) == 0 ) )
    {
        /* Save the address field. All frames are passed to the upper layed
         * and the decision if a frame is used is done there.
         */
        *pucRcvAddress = ucASCIIBuf[MB_SER_PDU_ADDR_OFF];

        /* Total length of Modbus-PDU is Modbus-Serial-Line-PDU minus
         * size of address field and CRC checksum.
         */
        *pusLength = ( USHORT )( usRcvBufferPos - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_LRC );

        /* Return the start of the Modbus PDU to the caller. */
        pucFrame = ( UCHAR * ) & ucASCIIBuf[MB_SER_PDU_PDU_OFF];
    }
    else
    {
        eStatus = MB_EIO;
    }
    EXIT_CRITICAL_SECTION(  );
    return eStatus;
}

    重点关注这个语句:pucFrame = ( UCHAR * ) & ucASCIIBuf[MB_SER_PDU_PDU_OFF];,这句话的意思是将形式参数的值进行改变。
    当调用具体函数的时候eMBASCIIReceive( rcvaddress, pFrame, plen )的时候,首先将实参的值等于形参的值,然后函数体内又对形参值进行了改变,而实参pFrame并没有得到改变。如果将形参变量变成了UCHAR ** pucFrame,我们再来分析:

    当函数调用的时候,变成了eMBASCIIReceive( rcvaddress, pFrame, &plen );那么

    pucFrame = &pFrame;

    在函数体内,pucFrame 指向pFrame。对*pucFrame 进行修改,就是对pFrame的值进行修改。

    所有,这儿要传递地址必须采用指向指针的指针才形。

系统分类: 嵌入式
用户分类: 读书笔记
标签: 无标签
来源: 整理
发表评论 阅读全文(456) | 回复(1)

0

关于投票
音视频基础知识-端子

介绍了用在音视频设备上的端子,包括S端子、复合音视频端子、VGA等等。

pdf

系统分类: 消费电子
用户分类: 读书笔记
标签: 无标签
来源: 整理
发表评论 阅读全文(472) | 回复(0)

0

关于投票
读书笔记之《C语音嵌入式系统开发》

        前几天粗粗地读了Michael J.Pont编著的《C语音嵌入式系统开发》,感觉写的非常浅显易懂,并且似乎也不知不觉学到了一些知识点。我下面将记录我在读这书中的心得。

        嵌入式系统是一种应用系统,它至少包含一个可编程的计算机。

        第一章 嵌入式系统中的C语音编程

         先摘录一段文字:“如果每个项目开放都是从头编写新代码的话,没有一家软件公司在业务上可以基业常青。编程语言必须支持创建灵活方便的库,这样同类的项目可以重用那些经过充分测试的代码模块。当使用新的处理器或升级处理器时,整个代码系统移植到新系统应该是可行的,并且尽可能少费劲。”

         第二章 8051系列微控制器简介

          对于大多数现代CMOS芯片,功耗和EMI(电磁干扰)与震荡频率之间成类似线形的关系。因此需要选择合适的震荡频率,来平衡性能功耗和干扰。

         在台式PC上,当运行程序时候,程序代码常从磁盘复制到RAM中,程序从RAM执行。而大多数嵌入式系统中,程序从ROM就地执行。这就使得嵌入式系统对RAM的需求变少。RAM只用来存储程序执行时候的变量以及程序的堆栈。

         第三章 你好,嵌入式世界

         本章主要讲了如何利用KEIL软件实现软件仿真。特殊寄存器的定义和使用有些特殊。对应于每个特殊寄存器,我们都在头文件中进行声明和定义。如:

                      sfr P0 = 0x80;

          使用该寄存器的时候,只需要简单赋值,如:

                         P0 = 0xFF;

           如果定义了P0是个地址,那么给地址赋值的话应该是*P0 = 0xFF;但是,编译器知道如何处理特殊寄存器,故可以直接赋值。

           第四章  读取开关值

           本章给出了读两种不同的开关的实现代码。一种是判断开和关的状态,如开灯关灯。另外一种是需要判断何时按键松开,如数山羊计数等。同样需要主要的是20ms左右的抖动,具体的抖动时间可以看示波器。

           第五章 为代码添加结构

           本章是有关编程风格的论述。讲的也非常好。提出的关键点主要有3个。1是使用C来编写面向对象风格的代码。2是使用端口头文件,搜集项目使用的所有的端口的信息。3是项目头文件的创建和使用,封装硬件环境的各主要方面,如芯片类型、震荡频率等等信息。

           面向对象的C语音编程,方法是创建并使用文件类。将一个功能写在一个文件中,规定好成员函数和变量。将这个功能当作一个类来使用,并且可以方便地移植到其他项目中。

            规定好成员函数和变量后,就需要仔细部署这些东西,需要注意的是:

            公有成员函数在.c文件中实现,但是要在.h中声明。

            私有的成员函数只需要在.c文件中声明。并且声明的时候可以在函数前面加static进行修饰。

            公有的常数要在.h文件中定义,主要是指宏吧。

            私有变量和常数要在.c中定义。

            少数的一些全局变量要在.c中定义??对于这一点,可能还有更方便的写法。可以借鉴ucos那本书的方式。将全局变量统一写在.h中,全局变量前面用一个统一的宏来修饰,当第一次编译的时候,如果这个宏得到了定义,则这个变量被识别并且分配相应的内存空间,当第二次碰到的时候,宏没有被定义,则这个宏变成一个extern关键字,说明这个变量是一个外部全局变量,不会再分配空间了。还有种说法:.h文件中不要定义变量,因为每次编译的时候,都会给变量分配空间。

        第六章 满足实时性的限制条件

         开始考虑精确测量时间的问题,因为许多嵌入式系统必须满足实时性的限制条件。嵌入式系统的一个重要特性就是需要迅速地处理输入并产生输出,且必须以毫秒为计量尺度。

         实时性最怕的是while语句,这个意味着CPU会一直查询一个条件。

          本章讨论了两种延时,一种是软件延时,另外一种是使用定时器来实现精确延时。这些延时都是通过while来查询来实现的。

          如何克服while带来的不利影响哪?可以使用“循环定时溢出”。例如我们对AD转换数值,通常采用的语句是:

                  while ( (ADCON & ADC1)==0);

           上面的语句可能会碰到问题,导致CPU一直查询,程序不能继续向下执行。解决的办法是采用“循环定时溢出”,如下:

                  while (((ADCON &ADC1) == 0) && (++Timeout_loop != 0));

           这样,即使有一些原因导致 (ADCON & ADC1)==0一直成立,也会因为(++Timeout_loop != 0)不成立而推出循环,让程序不至于死等在原地。

            上面的定时溢出是软件实现的,可能不太精确,从而可以通过创建硬件定时溢出来实现精确的定时溢出,如:

                  while( ((ADCON & ADC1) == 0) && !TF0);

          以后写while的时候,一定要多想一个问题:这个while怎么样才能保证退出?

          第七章 创建嵌入式操作系统

          超级循环架构的缺陷是很难在相同的时间间隔来执行目的函数(任务)。为了得到周期性的函数执行且避免浪费处理器的周期,可以使用定时器中断。具体办法是在规律且精确的时间间隔产生中断(一个tick),此中断可以用来周期性地调用合适的用户函数(任务)。使用这个操作系统的时候,需要注意两个问题:第一最坏情况下任务的执行时间要小于tick时间。第二是每个微控制器一个中断原则。

          第八章 多状态系统和函数序列

          上面的定时中断驱动的操作系统适合于周期性的执行任务的能力。多状态系统有如下的特点:涉及一系列的系统状态,每个状态可能要调用一个或者多个函数,存在用来定义状态之间转换的规则,状态转换时,可那会调用一个或者多个函数。可以分成2类,多状态时间驱动,即状态转换只取决于时间。多状态输入/时间驱动式,即状态转换取决与时间的推移和系统的输入。实现方式是在每个状态都计量时间,到达时间后转换状态。

          第九章 使用串行接口

          RS232常被用作某种形式的数据流控制。异步协议可以有2.5%波特率的波动。可以由软件或者硬件实现,容许数据接收端通知发送端暂停数据传输。尽管可以使用硬件联络,这却需要额外的信号线。数据流控制最普通的方法就是Xon/Xoff控制(发送端发送一个字节的数据,接收端有能力接收更多的数据,他什么也不做,发送端又发送一个字节的数据,直到接收端不能再接收更多的数据,于是他给发送端返回发送控制s(Xoff)字符,发送端收到Xoff后停止发送,如果接收端准备好了,他发送控制q(Xoff)字符给发送端,发送端继续发送数据,这样周而复始。)

          软件采用的思路是,不是将所有的数据立即全部发送,而只是将想要发送的数据存储在一个缓冲区内,没过一个时间检查缓冲区内的数据,且发送一个字符。我们不需要等待每个字符发送完,发送数据本身的程序执行非常快,数据发送过程本身不占用CPU时间。发送数据的时候,先查询是否发送完了,然后在将需要发送的数据放入缓冲区,然后退出就行。如下:

    while( (++Timeout1) &&(TI == 0);

     SBUF= char;

          第十一章 学习总结

           本书的目的如下:

           考虑了嵌入式系统中处理器及编程语言的选择,展示了怎样在嵌入式C语言程序中利用面向对象的设计和编程中的关键特性,以达到了在以后的项目中代码易重复使用的目标。描述了在许多简单的嵌入式应用程序中使用的超级循环软件架构。描述了一种简单的嵌入式操作系统,适合于很广范围的嵌入式应用程序。这个操作系统由定时器中断驱动,提供了精确的系统时间控制,却占用不到1%的处理器功能。研究了控制单个端口管脚状态的方法。研究了端口管脚的读取以及机械开关的读取。描述了如何使用8051芯片的串行端口。提供了一个案例分析,说明怎么样在实际的应用中将以上所有的内容组织起来。

 

         

系统分类: 嵌入式
用户分类: 读书笔记
标签: 无标签
来源: 原创
发表评论 阅读全文(586) | 回复(0)
总共 , 当前 /