条件编译和内存操作

一·条件编译

       在程序设计时,可以根据条件编译告诉编译器的条件执行或者忽略代码。例如在配置从机(eg: 模拟前端)的寄存器时,需要确认数据写入是否成功,进行读出和重写!此时可选择性的选择是否重写和重写次数。如下:


   
   
  1. SendDataToAddr(ADDR_AFE_BIM1,Body_FWR_50KHz);
  2. #if(DEBUG_CS1258_REWRITE == 1)
  3. uint8 count = REREAD_REWRITE_NUM;
  4. while(count != 0){
  5. if(ReadAddrData(ADDR_AFE_BIM1) != Body_FWR_50KHz){
  6. myPrintf(MY_PRINTF_ERROR, "%s(%d):SendDataToAddr error;\r\n",__func__,__LINE__);
  7. DelayTimeMs( 10);
  8. SendDataToAddr(ADDR_AFE_BIM1,Body_FWR_50KHz);
  9. }
  10. else{
  11. break;
  12. }
  13. count--;
  14. }
  15. if(count == 0){
  16. myPrintf(MY_PRINTF_ERROR, "%s(%d):count == 0;\r\n",__func__,__LINE__);
  17. }
  18. #endif

条件编译规则如下:


   
   
  1. #ifdef MAVIS //如果定义了MAVIS,则执行下面的条件
  2. {
  3. ;
  4. }
  5. #else //如果未定义了MAVIS,则执行下面的条件 //#else 可省略
  6. {
  7. ;
  8. }
  9. #endif
  10. // 备注: C只编译符合条件的模块;
  11. // 例如:
  12. #ifdef CUSTOMIZED
  13. // thread 0
  14. {
  15. "Dsp1-thread0", // thread name
  16. Alg_init, // init function
  17. Alg_prep, // pre-process function
  18. Alg_proc, // main process function
  19. Alg_deinit, // de-init function
  20. TRUE, // need output buffer?
  21. },
  22. #endif
  23. // thread 1,
  24. {
  25. "", //Dsp1-thread1", // thread name
  26. NULL, // init function
  27. NULL, // pre-process function
  28. NULL, // main process function
  29. NULL, // de-init function
  30. FALSE, // need output buffer
  31. }

      再例如:在定义头文件时,为了避免重复定义使用#ifndef  #endif来定义。


   
   
  1. #ifndef __USER_CS1258_H__
  2. #define __USER_CS1258_H__
  3. #define U8 unsigned char // 关键字重定义
  4. #define U16 unsigned short
  5. #define U32 unsigned int
  6. #include "esp8266.h" // 库文件包含
  7. #define EVENT_DISABLE_CS1258_TEST 0 // 明示常量
  8. typedef struct CS1258Event_s{ // 结构体定义
  9. char event; //事件
  10. int dat; //数据
  11. }CS1258Event_t;
  12. void userCs1258Init(void); // 函数原型声明
  13. #endif
  14. /*
  15. 备注:
  16. 1) 一个功能中,包含一个.C文件和一个.h文件。在.h文件中最好将用#ifndef #endif将 关键字重定义,库文件包含,宏参数定义,结构体定义,函数声明等包含进去。这样,头文件可以避免被多次包含。头文件中定义的变量不存在重复声明或定义。
  17. 2)__USER_CS1258_H__ :下划线“__”属于编程风格的内容,对程序没有影响。不用下划线也可以,用几个下划线也由个人习惯。需要大写是因为其实质是一个宏名,宏名需要大写,其次下划线是为了容易区分,避免命名重复;注:标准保留使用下划线作为前缀, 所以在自己的代码中不要这样写, 避免与标准头文件中的宏发生冲突。
  18. 3)C语言一般支持8层以上的文件包含;
  19. 4)当预处理器首次发现该文件被包含时,__USER_CS1258_H__ 是未定义的, 所以定义了__USER_CS1258_H__ , 并接着处理该文件的其他部分。 当预处理器第2次发现该文件被包含时, __USER_CS1258_H__ 是已定义的,所以预处理器跳过了该文件的其他部分。
  20. */

     再例如,运用#if  #elif  #elif #else #endif 来进行条件编译


   
   
  1. #if defined (IBMPC) //可以用 #if defined(VAX) 代替 #ifndef VAX 和#elif 一起使用;
  2. #include "ibmpc.h"
  3. #elif defined (VAX)
  4. #include "vax.h"
  5. #elif defined (MAC)
  6. #include "mac.h"
  7. #else
  8. #include "general.h"
  9. #endif

 

二·内存操作

数据指针


在嵌入式系统的编程中,常常要求在特定的内存单元读写内容,汇编有对应的MOV指令,而除C/C++以外的其它编程语言基本没有直接访问绝对地址的能力。在嵌入式系统的实际调试中,多借助C语言指针所具有的对绝对地址单元内容的读写能力。以指针直接操作内存多发生在如下几种情况: 
  

(1) 某I/O芯片被定位在CPU的存储空间而非I/O空间,而且寄存器对应于某特定地址; 
  

(2) 两个CPU之间以双端口RAM通信,CPU需要在双端口RAM的特定单元(称为mail box)书写内容以在对方CPU产生中断; 
  

(3) 读取在ROM或FLASH的特定单元所烧录的汉字和英文字模。 

 

譬如: 
 

  •  
unsigned char *p = (unsigned char *)0xF000FF00;*p="11";
   
   

 

以上程序的意义为在绝对地址0xF0000+0xFF00(80186使用16位段地址和16位偏移地址)写入11。 

  

在使用绝对地址指针时,要注意指针自增自减操作的结果取决于指针指向的数据类别。上例中p++后的结果是p= 0xF000FF01,若p指向int,即: 
 

  •  
int *p = (int *)0xF000FF00;
   
   

 

p++(或++p)的结果等同于:p = p+sizeof(int),而p-(或-p)的结果是p = p-sizeof(int)。 


记住:CPU以字节为单位编址,而C语言指针以指向的数据类型长度作自增和自减。理解这一点对于以指针直接操作内存是相当重要的。 

 

函数指针


首先要理解以下三个问题: 
  

(1)C语言中函数名直接对应于函数生成的指令代码在内存中的地址,因此函数名可以直接赋给指向函数的指针; 
  

(2)调用函数实际上等同于"调转指令+参数传递处理+回归位置入栈",本质上最核心的操作是将函数生成的目标代码的首地址赋给CPU的PC寄存器; 
  

(3)因为函数调用的本质是跳转到某一个地址单元的code去执行,所以可以"调用"一个根本就不存在的函数实体,晕?请往下看: 
  

请拿出你可以获得的任何一本大学《微型计算机原理》教材,书中讲到,186 CPU启动后跳转至绝对地址0xFFFF0(对应C语言指针是0xF000FFF0,0xF000为段地址,0xFFF0为段内偏移)执行,请看下面的代码: 

 

typedef void (*lpFunction) ( ); /* 定义一个无参数、无返回类型的 *//* 函数指针类型 */lpFunction lpReset = (lpFunction)0xF000FFF0; /* 定义一个函数指针,指向*//* CPU启动后所执行第一条指令的位置 */lpReset(); /* 调用函数 */
   
   

 

在以上的程序中,我们根本没有看到任何一个函数实体,但是我们却执行了这样的函数调用:lpReset(),它实际上起到了"软重启"的作用,跳转到CPU启动后第一条要执行的指令的位置。 
  

记住:函数无它,唯指令集合耳;你可以调用一个没有函数体的函数,本质上只是换一个地址开始执行指令! 

 

数组vs.动态申请


在嵌入式系统中动态内存申请存在比一般系统编程时更严格的要求,这是因为嵌入式系统的内存空间往往是十分有限的,不经意的内存泄露会很快导致系统的崩溃。 
  

所以一定要保证你的malloc和free成对出现,如果你写出这样的一段程序: 
 

char * function(void){ char *p; p = (char *)malloc(…); if(p==NULL)  …;  … /* 一系列针对p的操作 */ return p;}在某处调用function(),用完function中动态申请的内存后将其free,如下:char *q = function();free(q);
   
   

 

上述代码明显是不合理的,因为违反了malloc和free成对出现的原则,即"谁申请,就由谁释放"原则。不满足这个原则,会导致代码的耦合度增大,因为用户在调用function函数时需要知道其内部细节! 

  

正确的做法是在调用处申请内存,并传入function函数,如下: 
 

char *p="malloc"(…);if(p==NULL)…;function(p);free(p);p="NULL"; 而函数function则接收参数p,如下:void function(char *p){ … /* 一系列针对p的操作 */}
   
   

 

基本上,动态申请内存方式可以用较大的数组替换。对于编程新手,笔者推荐你尽量采用数组!嵌入式系统可以以博大的胸襟接收瑕疵,而无法"海纳"错误。毕竟,以最笨的方式苦练神功的郭靖胜过机智聪明的杨康。 
  

给出原则: 
 

(1)尽可能的选用数组,数组不能越界访问(真理越过一步就是谬误,数组越过界限就光荣地成全了一个混乱的嵌入式系统); 
  

(2)如果使用动态申请,则申请后一定要判断是否申请成功了,并且malloc和free应成对出现! 

 

const在C++语言中则包含了更丰富的含义,而在C语言中仅意味着:"只能读的普通变量",可以称其为"不能改变的变量"(这个说法似乎很拗口,但却最准确的表达了C语言中const的本质),在编译阶段需要的常数仍然只能以#define宏定义!故在C语言中如下程序是非法的:

 

关键字const


const意味着"只读"。区别如下代码的功能非常重要,也是老生长叹,如果你还不知道它们的区别,而且已经在程序界摸爬滚打多年,那只能说这是一个悲哀: 

 

const int a;int const a;const int *a;int * const a;int const * a const;const int SIZE = 10;char a[SIZE]/* 非法:编译阶段不能用到变量 */ 
   
   

 

关键字volatile


volatile变量可能用于如下几种情况: 
  

(1) 并行设备的硬件寄存器(如:状态寄存器,例中的代码属于此类); 
  

(2) 一个中断服务子程序中会访问到的非自动变量(也就是全局变量); 
  

(3) 多线程应用中被几个任务共享的变量。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值