《嵌入式C语言进阶之道》由会员分享,可在线阅读,更多相关《嵌入式C语言进阶之道(75页珍藏版)》请在金锄头文库上搜索。
1、 嵌入式 C 进阶之道 版本:V1.0 瓶盖 水羽哥(蝈蝈) 2010-8-31(发布) 前言 C 语言的书有一大堆,嵌入 C 语言的书也不少,但都不过是简单介绍一下标准 C 语言的 语法,再讲一下嵌入式 C 语言与标准 C 的区别,讲一下新增加的关键字。这样的书,对于 初学者或许是适合的。问题是,若是我不愿意只是当一个小菜鸟,我想对嵌入式 C 语言有 更多更深的了解时,我突然发现,满图书馆的书,竟找不到一本,能解我心头之惑。 对于在实际工作当中, 你不再是独自编写程序,你要和小组内的成员之间分工合作,你 要学会模块化编程、要写出更规范更安全的代码、做更合理的优化、减少更多的 bug。所有 的
2、这些,都迫使你必须更彻底的理解嵌入式 C 的语法结构,数据细节,与硬件打交道的特 性,使得你必须时时考虑硬件与 C 的对应关系并养成良好的编程习惯。 本文的原意是想尽可能多的解决上述问题,帮助更多的新人深入理解嵌入式 C 语言。 本文是我工作之余整理而来。是对我个人学习嵌入式 C 语言过程的总结。本文涉及的 知识点多数来自于网络,其中加入我个人理解以及自己平时遇到的注意点。 本文从编程风格谈起, 讲述了模块化的编程方法, 对一些大型项目中常用重点关键字做 了讲解,参照 MISRA C 2004 规范 ,对嵌入式 C 安全编程做了阐述,对一些嵌入式 C 的小技 巧进行了讨论。 在读本文时,我假设
3、你有 C 语言基础、至少理解一种微控制器(51、PIC、AVR、DSP、 ARM 等) 、有简单的(这里本想写较深的)汇编基础。 声明:本人不拥有该文档的版权,任何人可随意传播。本人不对文中任何事物负责, (呵呵, 工作比较忙,交流尽量通过邮箱) ,但会不定期发布新版本以改正前版的不足。希望本文能 帮助一些热爱嵌入式编程的新人们,所以,如果您有更好的建议、发现本文档的错误,请将 以上详细信息发到我邮箱,我会在新版本后面,注明您的名字(或者网名) 。希望广大的嵌 入式爱好者们能共同努力,为后来者们铺一个更平坦的大道。 我的邮箱: 技术博客:http:/ 瓶盖 水羽哥 2010-7-9 养成良好的
4、编程风格 1 / 7 一一. .良好的编程风格良好的编程风格 编程的总则: 编程首要是要考虑程序的可行性,然后是可读性、可移植性、健壮性以及 可测试性。大多数程序员只是关注程序的可行性,而忽略了可读性,可移植性和健壮性,其实 我个人认为,程序的可行性和健壮性与程序的可读性有很大的关系,能写出可读性很好的程 序的程序员,他写的程序的可行性和健壮性必然不会差,也会有不错的可移植性.程序的可读 性需要程序员有一个良好的编程风格. 好风格应该成为一种习惯。 如果你在开始写代码时就关心风格问题, 如果你花时间去审 视和改进它,你将会逐渐养成一种好的编程习惯。一旦这种习惯变成自动的东西,你的潜意 识就会帮
5、你照料许多细节问题,甚至你在工作压力下写出的代码也会更好。 1.1.排版排版 a a. . 代码缩进空格数为4 4个。若是可能,尽量用空格来代替Tab键,因为有些编译器不支持Tab 键(我自己至今未见过,但确实有这个风险),这给程序的移植带来了问题。在keil中这个 问题很容易解决,只需在在keil主界面的菜单栏点击EditConfiguration,弹出 Configuration窗口,点击Editor标签,在其中C/C+ File:、ASM、Other Files栏下,选 中Insert spaces for tab:复选框,Tab对应的框中填4,这样按tab键就相当于按下四个空 格键。
6、BOOL BufClr(UINT8 * dest,UINT32 size)BOOL BufClr(UINT8 * dest,UINT32 size) if(NULL =dest | NULL=size)if(NULL =dest | NULL=size) return FALSE;return FALSE; b b. . 较长的语句要分2 2行来书写,并用符号隔开。 uncrc=calcCRC16(Packet.p,unlen);uncrc=calcCRC16(Packet.p,unlen); if(UINT8) uncrc != Packet.down_ser.mCrc0 if(UINT8)
7、 uncrc != Packet.down_ser.mCrc0 |(UINT8)(u|(UINT8)(uncrc8)!= Packet.down_ser.mCrc1)ncrc8)!= Packet.down_ser.mCrc1) BELL(ON);BELL(ON); c c. . 函数代码的参数过长,分多行来书写。 void UARTSendAndRecv(UINT8 *ucSendBuf,void UARTSendAndRecv(UINT8 *ucSendBuf, UINT8 ucSendLength,UINT8 ucSendLength, UINT8 *ucRecvBuf,UINT8 *u
8、cRecvBuf, UINT8 ucRecvLength)UINT8 ucRecvLength) d d. if. if、dodo、whilewhile、switchswitch、forfor、casecase、defaultdefault等关键字,必须加上大括号。 if(bSendEnd)if(bSendEnd) BELL(ON);BELL(ON); 养成良好的编程风格 2 / 7 elseelse BELL(OFF);BELL(OFF); /- for(i=0; i.h void void funcfunc(void)(void) c c. . 函数头应该进行注释,例如函数名称、输入参数、
9、返回值、功能说明。 /*将将所有参数写入所有参数写入 AT24C64,共共 4 字节字节* *说明说明:将表号和用户电量共四字节数据写入将表号和用户电量共四字节数据写入 AT24C64 中中 *入口参数入口参数: * 1.数据间接寻址地址数据间接寻址地址-buf * 2.写入到写入到 AT24C64 的地址字的地址字-addh,addrl * 3.写入字节数写入字节数-count *出口参数出口参数:1 表示写成功表示写成功,0 表示写失败表示写失败 */ bit write_byte(unsigned char * buf,bit write_byte(unsigned char * buf
10、, unsigned char addrh,unsigned char addrh, unsigned char addrl,unsigned char addrl, unsigned char count)unsigned char count) d d. . 全局变量要注释其功能,若为关键的局部变量同样需要注释其功能。 volatile UINT8 _ucSysMsg=SYS_IDLE;volatile UINT8 _ucSysMsg=SYS_IDLE; void SYSSetMsgPriority(void)void SYSSetMsgPriority(void) SYSMSG Msgt
11、;/SYSMSG Msgt;/临时存储消息临时存储消息 UINT8 i;UINT8 i; e e. . 复杂的宏定义同样要加上注释。 /* SYS_MSG_MAP 建立一个消息映射 宏参数NAME:消息映射表的名字 宏参数NUM_OF_MSG:消息映射的个数 */ #define SYS_MSG_MAP(NAME,NUM_OF_MSG) do#define SYS_MSG_MAP(NAME,NUM_OF_MSG) do DEFINE_MSG_NAME(NAME);DEFINE_MSG_NAME(NAME); UINT8 i;UINT8 i; for(i=0;i= b int j=0; C 的精
12、华关键字 4 / 7 . i=j; /非法非法,导致编译错误,导致编译错误,因为只能被读因为只能被读 j=i; /合法合法 b. const关键字修饰的变量在声明时必须进行初始化。如下代码: const int i=5; /合法合法 const int j; /非法,导致编译错误非法,导致编译错误 c. 用const声明的变量虽然增加了分配空间,但是可以保证类型安全。const最初是从 C+变化得来的,它可以替代define来定义常量。在旧版本(标准前)的c中,如果想建立一个 常量,必须使用预处理器: #define PI 3.14159 此后无论在何处使用PI,都会被预处理器以3.14159
13、替代。编译器不对PI进行类型检查,也 就是说可以不受限制的建立宏并用它来替代值,如果使用不慎,很可能由预处理引入错误, 这些错误往往很难发现。而且,我们也不能得到PI的地址(即不能向PI传递指针和引用)。 const的出现,比较好的解决了上述问题。 d. C标准中,const定义的常量是全局的。 e. 必须明白下面语句的含义,我自己是反复记忆了许久才记住,方法是:若是想定义 一个只读属性的指针,那么关键字const要放到* 后面。 char *const cp; /指针不可改变,但指向的内容可以改变 char const *pc1; /指针可以改变,但指向的内容不能改变 const char
14、*pc2; /同上(后两个声明是等同的) f. 将函数传入参数声明为const,以指明使用这种参数仅仅是为了效率的原因,而不是 想让调用函数能够修改对象的值。 参数const通常用于参数为指针或引用的情况, 且只能修饰输入参数;若输入参数采用“值 传递”方式,由于函数将自动产生临时变量用于复制该参数,该参数本就不需要保护,所以 不用const修饰。例子: void fun0(const int * a ); void fun1(const int 调用函数的时候,用相应的变量初始化const常量,则在函数体中,按照const所修饰的 部分进行常量化,如形参为const int * a,则不能对传递进来的指针所指向的内容进行改变, 保护了原指针所指向的内容;如形参为const int int main(void) . while (1) if (i) dosomething(); /* Interrupt service routine. */ void ISR_2(void) i=1; 程序的本意是希望ISR_2中断产生时,在main当中调用dosomething函数,但是,由于 编译器判断在main函数里面没有修改过i,因此可能只执行一次对从i到某寄存器的读操作, 然后每次if判断都只使用这个寄