一·条件编译
在程序设计时,可以根据条件编译告诉编译器的条件执行或者忽略代码。例如在配置从机(eg: 模拟前端)的寄存器时,需要确认数据写入是否成功,进行读出和重写!此时可选择性的选择是否重写和重写次数。如下:
-
SendDataToAddr(ADDR_AFE_BIM1,Body_FWR_50KHz);
-
#if(DEBUG_CS1258_REWRITE == 1)
-
uint8 count = REREAD_REWRITE_NUM;
-
while(count !=
0){
-
if(ReadAddrData(ADDR_AFE_BIM1) != Body_FWR_50KHz){
-
myPrintf(MY_PRINTF_ERROR,
"%s(%d):SendDataToAddr error;\r\n",__func__,__LINE__);
-
DelayTimeMs(
10);
-
SendDataToAddr(ADDR_AFE_BIM1,Body_FWR_50KHz);
-
}
-
else{
-
break;
-
}
-
count--;
-
}
-
if(count ==
0){
-
myPrintf(MY_PRINTF_ERROR,
"%s(%d):count == 0;\r\n",__func__,__LINE__);
-
}
-
#endif
条件编译规则如下:
-
#ifdef MAVIS //如果定义了MAVIS,则执行下面的条件
-
{
-
;
-
}
-
#else //如果未定义了MAVIS,则执行下面的条件 //#else 可省略
-
{
-
;
-
}
-
#endif
-
-
// 备注: C只编译符合条件的模块;
-
-
// 例如:
-
-
#ifdef CUSTOMIZED
-
// thread 0
-
{
-
"Dsp1-thread0",
// thread name
-
Alg_init,
// init function
-
Alg_prep,
// pre-process function
-
Alg_proc,
// main process function
-
Alg_deinit,
// de-init function
-
TRUE,
// need output buffer?
-
},
-
-
#endif
-
// thread 1,
-
{
-
"",
//Dsp1-thread1", // thread name
-
NULL,
// init function
-
NULL,
// pre-process function
-
NULL,
// main process function
-
NULL,
// de-init function
-
FALSE,
// need output buffer
-
}
再例如:在定义头文件时,为了避免重复定义使用#ifndef #endif来定义。
-
#ifndef __USER_CS1258_H__
-
#define __USER_CS1258_H__
-
-
#define U8 unsigned char // 关键字重定义
-
#define U16 unsigned short
-
#define U32 unsigned int
-
-
#include "esp8266.h" // 库文件包含
-
-
#define EVENT_DISABLE_CS1258_TEST 0 // 明示常量
-
-
typedef
struct CS1258Event_s{
// 结构体定义
-
char event;
//事件
-
int dat;
//数据
-
}CS1258Event_t;
-
-
void userCs1258Init(void);
// 函数原型声明
-
-
#endif
-
/*
-
备注:
-
1) 一个功能中,包含一个.C文件和一个.h文件。在.h文件中最好将用#ifndef #endif将 关键字重定义,库文件包含,宏参数定义,结构体定义,函数声明等包含进去。这样,头文件可以避免被多次包含。头文件中定义的变量不存在重复声明或定义。
-
2)__USER_CS1258_H__ :下划线“__”属于编程风格的内容,对程序没有影响。不用下划线也可以,用几个下划线也由个人习惯。需要大写是因为其实质是一个宏名,宏名需要大写,其次下划线是为了容易区分,避免命名重复;注:标准保留使用下划线作为前缀, 所以在自己的代码中不要这样写, 避免与标准头文件中的宏发生冲突。
-
3)C语言一般支持8层以上的文件包含;
-
4)当预处理器首次发现该文件被包含时,__USER_CS1258_H__ 是未定义的, 所以定义了__USER_CS1258_H__ , 并接着处理该文件的其他部分。 当预处理器第2次发现该文件被包含时, __USER_CS1258_H__ 是已定义的,所以预处理器跳过了该文件的其他部分。
-
*/
再例如,运用#if #elif #elif #else #endif 来进行条件编译
-
#if defined (IBMPC) //可以用 #if defined(VAX) 代替 #ifndef VAX 和#elif 一起使用;
-
#include "ibmpc.h"
-
#elif defined (VAX)
-
#include "vax.h"
-
#elif defined (MAC)
-
#include "mac.h"
-
#else
-
#include "general.h"
-
#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) 多线程应用中被几个任务共享的变量。