资源描述
单击此处编辑母版标题样式,单击此处编辑母版文本样式,第二级,第三级,第四级,第五级,Page,*,单击此处编辑母版标题样式,单击此处编辑母版文本样式,第二级,第三级,第四级,第五级,嵌入式系统C语言编程基础,Page,2,目录,关于本课程,C,语言复习,编程规范,开发高效程序的技巧,Page,3,为什么学习本课程,会,C,语法与会用,C,语言写程序是两个概念,C,的灵活性以及由此而产生的陷阱非常多,The Most Beautiful Language and Most Dangerous Language in the Programming World!,本课程将嵌入式系统中最基本的(最经常用到的)概念与技巧介绍给大家,Page,4,小测验?,Page,5,Quiz 1,所有嵌入式系统的主流程最后都进入一个死循环,怎样用,C,语言实现一个死循环?,Page,6,Quiz 2,while().,和,do.while(),有什么区别?,Page,7,Quiz 3,用变量,a,给出下列定义:,一个整型数,一个指向整型数的指针,一个有,10,个整型数的的数组,一个有,10,个指针的数组,该指针是指向一个整型数的,Page,8,Quiz 4,关键字,static,的作用是什么?,Page,9,Quiz 5,关键字,const,的作用是什么?,Page,10,Quiz 6,定义一个标准宏,MIN,,这个宏输入两个参数并返回较小的一个。,Page,11,Quiz 7,嵌入式系统中经常要对变量或寄存器进行位操作。给定一个,int,型变量,a,,写两段代码,第一个将,a,的,bit 3,置为,1,,第二个将,a,的,bit 3,置为,0,。以上两个操作中,要保持其它位不变。,Page,12,Quiz 8,嵌入式系统具有经常去直接访问物理地址的特点。在某工程中,要求设置一绝对地址为,0 x5a00,的整型变量的值为,0 xaa55,。写代码去完成这一任务。,Page,13,Quiz 9,下面这段代码的输出是什么?,void foo(void)unsigned int a=6;int b=-20;(a+b 6)?puts(6):puts(6);,Page,14,Quiz 10,请评论下面一段程序代码:,void test()char string10;char*str=“0123456789”;strcpy(string,str);,Page,15,Quiz 11,请评论下面一段程序代码:,void GetMemory(char*p)p=(char*)malloc(0 x20);void Test(void)char*str=NULL;GetMemory(str);strcpy(str,”Hello World!”);printf(str);,Page,16,Quiz 12,中断是嵌入式系统的重要组成部分。请评论下面一段中断服务子程序代码:,_interrupt double compute_area(double radius)double area=PI*radius*radius;printf(nArea=%f,area);return area;,Page,17,Answer!,Page,18,Quiz 1 Answer,while(1).,Page,19,Quiz 2 Answer,while().,为入口条件循环,即在每次执行循环之前先检查判断条件;,do.while(),为退出条件循环,即在执行循环之后再检查判断条件。,Page,20,Quiz 3 Answer,a)int a;b)int*a;c)int a10;d)int*a10;,Page,21,Quiz 4 Answer,在,C,语言中,关键字,static,有三个明显的作用:,1),在函数体内,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。,2),在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。,3),在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用。,Page,22,Quiz 5 Answer,const,是,constant,的缩写,“恒定不变”的意思。被,const,修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。,const,常量与,#define,常量:前者有数据类型,后者只是字符替换(而且可能产生意料不到的错误),所以编译器可以对前者进行安全性检查。,Page,23,Quiz 6 Answer,#define MIN(A,B)(A)=(B)?(A):(B),#define,宏的副作用,下面的代码执行后会发生什么事:,least=MIN(*p+,b);,Page,24,Quiz 7 Answer,const int BIT3=0 x016”,,原因是当表达式中存在有符号类型和无符号类型时所有的操作数都自动转换为无符号类型。因此,-20,变成了一个非常大的正整数,所以该表达式计算出的结果大于,6,。这一点对于频繁用到无符号数据类型的嵌入式系统来说是丰常重要的。,Page,27,Quiz 10 Answer,字符串,str,需要,11,个字节才能存放下(包括末尾的,0,),而,string,只有,10,个字节的空间,所以,strcpy,会导致数组越界。,Page,28,Quiz 11 Answer,编译器总是要为函数的每个参数制作临时副本,指针参数,p,的副本是,_p,,编译器使,_p=p,。本例中,,_p,申请了新的内存,但只是,_p,的值改变了,,p,的值却丝毫未变,仍然为,NULL,。事实上,每执行一次,GetMemory,就会泄漏一块内存。,Page,29,Quiz 12 Answer,1,),ISR,不能返回值;,2,)不能向,ISR,传递参数;,3,),ISR,应该是短而有效率的,因而在,ISR,中做浮点运算、使用,printf,等是不明智的,会经常有重入和性能上的问题。,Page,30,还是先复习一下,C,吧!,Page,31,C,语言复习,1,)循环与分支,2,)作用域与存储类,3,)内存与指针,4,)位操作,5,)预编译处理,Page,32,循环与分支,1,)几种循环语句:,for(initialize;test;update)body;while(test)body;dobody;while(test);,2,)循环辅助手段:,break,语句用于跳出循环,,continue,语句用于结束本次循环。,Page,33,循环与分支,3,)三种形式的,if,语句:,if(expression).if(expression).else.if(expression1).else if(expression).else.,Page,34,循环与分支,4,)条件运算符,?:,expression1?expression2:expression3,如果,expression1,为真,整个表达式的值为,expression2,的值,否则为,expression3,的值;,是,if.else.,的简写形式,但是编译器可以产生比,if.else.,更优化的代码。,Page,35,循环与分支,5,)多重选择:,switch,switch(expression)case constant1:statements;break;case constant2:statements;break;.default:statements;break;switch,判断表达式必须具有整数值,,case,标签必须是整数常量或整数常量表达式。,Page,36,C,语言复习,1,)循环与分支,2,)作用域与存储类,3,)内存与指针,4,)位操作,5,)预编译处理,Page,37,作用域与存储类,1,)作用域,代码块作用域(局部变量)代码块:包含在开始花括号和对应结束花括号之内的一段代码。作用域:从定义变量位置到该代码块的末尾。,文件作用域(全局变量)在所有函数之外定义的变量具有文件作用域。作用域:从定义变量位置到包含该定义的文件的结尾处。在其它位置使用该变量应用,extern,来声明该变量。,Page,38,作用域与存储类,2,)存储时期,静态存储时期:程序运行期间一直存在,给变量分配固定的存储空间;所有全局变量具有静态存储时期(具有静态存储时期的并不都是全局变量),但是修饰全局变量的关键词,static,表明的是链接类型,并非存储时期。,动态(自动)存储时期:运行期间根据需要动态的给变量分配存储空间。,Page,39,作用域与存储类,3,),5,种存储类,存储类,存储时期,作用域,链接,声明方式,自动,自动,代码块,空,代码块内,寄存器,自动,代码块,空,代码块内,使用关键字,register,外部静态,静态,文件,外部,所有函数之外,内部静态,静态,文件,内部,所有函数之外,使用关键字,static,代码块静态,静态,代码块,空,代码块内,使用关键字,static,Page,40,C,语言复习,1,)循环与分支,2,)作用域与存储类,3,)内存与指针,4,)位操作,5,)预编译处理,Page,41,内存与指针,1,)内存分配方式,从静态存储区分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,,static,变量;,从栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限;,动态内存分配。程序在运行的时候由程序员自己负责申请和释放内存。,Page,42,内存与指针,2,)内存陷阱,C,语言的灵活性,指针的应用,以及,C,语法的宽容性很容易造成代码的错误,这其中最主要的就是内存单元的溢出。,a),堆栈溢出,b),数组越界,.,将错误锁定在一个函数中以后,首先应该关注内存的问题。,Page,43,内存与指针,举例,看看下面这段代码有什么问题,char*DoSomething()char i32*1024;.return i;,两个重大问题:,1,、临时变量在堆栈上创建,太大的临时变量数组会冲掉堆栈;,2,、返回堆栈中的地址是没有意义的,因为堆栈中的内容永远是不确定的。,Page,44,内存与指针,数组越界的危险,临时数组:在栈上创建,因此临时数组越界有可能导致某些局部变量莫明其妙被改,函数返回时崩溃等等;,全局数组:在静态存储区创建,因此全局数据越界有可能导致某些全局变量莫明其妙被改,被冲的动态内存无法释放等等。,Page,45,内存与指针,3,)指针,指针是什么?指针是一个变量,它的值是另外一个变量的地址。,指针的类型指针所指向的变量的类型,就是指针的类型。举例,右边的运算有何不同:,int X2,*pX=,Page,46,内存与指针,指针的三要素,1,、指针指向的地址;,2,、指针指向地址上的内容;,3,、指针本身的地址;,举例:,int A,*pA,*ppA;pA=,Page,47,内存与指针,指针的大小(指针变量占用的内存空间),举例,以下为,ARM,平台下的一段,32,位,C,程序,请计算,sizeof,的值。,char str=“Hello”;char*p=str;int n=10;,sizeof(str)=?sizeof(p)=?sizeof(n)=?,sizeof(str)=6sizeof(p)=4sizeof(n)=4,Page,48,内存与指针,指针的初始化,指针变量在没有被初始化之前,它的值是随机的;一个指向不明的指针是非常危险的。,当创建一个指针时,系统只分配了用来存储指针本身的内存空间,并不分配用来存储数据的内存空间。使用指针之前,必须给它赋予一个已分配的内存地址。,Page,49,内存与指针,指针与数组,举例,下列操作是否合法:,int a4,*p;p=a;*(a+2)=0 x00;p2=0 x01;,/,等价于,p=&a0,;,/,等价于,a2,;,/,等价于*,(p+2),;,但是数组名不同于指针:数组名,a,是指向数组起始位置的“常量”。因此不能对数组名进行赋值操作。,a=p;/,错误,a+;/,错误,Page,50,内存与指针,指针与数组什么时候是相同的,举例,以下为,ARM,平台下的一段,32,位,C,程序,请计算,sizeof,的值。,void Func(char a100)sizeof(a)=?,sizeof(a)=4,(1),数组名作为函数形参时,在函数体内,其失去了本身的内涵,仅仅只是一个指针;,(2),很遗憾,在失去其内涵的同时,它还失去了其常量特性,可以作自增、自减等操作,可以被修改。,Page,51,内存与指针,数组与指针运用规则,数组,声明,使用,定义,如,char a10,不能写成指针的形式,extern,,如,extern char a,不能写成指针的形式,func(char a);,或,func(char*a),函数参数可以写成指针的形式,c=ai;,或,c=*(a+i);,可以写成指针的形式,Page,52,C,语言复习,1,)循环与分支,2,)作用域与存储类,3,)内存与指针,4,)位操作,5,)预编译处理,Page,53,位操作,1,)位操作的用途,硬件寄存器控制;,压缩和加密操作;,提高程序运行效率;,.,因为位是程序可以操作的最小数据单位,所以理论上可以用“位操作”完成所有的运算与操作。,Page,54,位操作,2,)位运算符,位逻辑运算符,&,位与(两个数对应位都为,1,结果才为,1,),|,位或(两个数对应位有一个为,1,结果则为,1,),位异或(两个数对应位不同结果则为,1,),按位取反(单目运算符),移位运算符,右移,Page,55,位操作,3,)用法:掩码,位操作符通常跟掩码一起用。掩码是某些位为开而某些位为关的位组合。例:,flag,0,0,0,0,0,0,1,0,1,0,1,1,0,1,1,0,0,0,0,0,0,0,1,0,MASK,flag,&,=,Page,56,位操作,4,)用法:打开位、关闭位、转置位,打开位:,flag=flag|MASK;,或,flag|=MASK;,关闭位:,flag=flag,或,flag,转置位:,flag=flag MASK;,或,flag=MASK;,Page,57,位操作,5,)用法:查看某一位的值,错误用法:,if(flag=MASK),正确用法:,if(flag&MASK)=MASK),位运算符的优先级低于,=,,因此需要在,flag&MASK,的两侧加上圆括号。,Page,58,位操作,6,)用法:移位操作,unsigned char ch=0 x07;/00000111,unsigned char i;,i=ch 2;,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,左移,右移,Page,59,C,语言复习,1,)循环与分支,2,)作用域与存储类,3,)内存与指针,4,)位操作,5,)预编译处理,Page,60,预编译处理,C,编译系统在对程序进行编译之前,先进行预处理。,C,提供的预处理功能主要有以下三种:,宏定义,文件包含,条件编译,Page,61,预编译处理,1,)宏定义,类对象宏例:,#define STX 0 x02,类函数宏例:,#define SQUARE(x)(x)*(x),几点说明:,a.,宏定义不是,C,语句,不在行末加分号;,b.,宏名有效范围为从定义处到本源文件结束;,c.,在宏定义时,可以引用已经定义的宏名;,d.,宏替换不占运行时间,只占编译时间;,Page,62,预编译处理,2,)文件包含:,#include,预处理器发现,#include,后,就会寻找后跟的这个文件并把这个文件的内容复制到当前位置替换,#include,指令;,使用,#include,指令的一些例子:,#include,从标准库路径开始搜索文件,#include“def.h”,从当前工作路径开始搜索文件,#include“/usr/head/def.h”,搜索“,/usr/head/”,目录,Page,63,预编译处理,3,)条件编译,条件编译指不对整个程序编译,而是编译满足条件的那部分。条件编译有以下几种形式:,a.#ifdef,标识符 程序段,1,;,#else,程序段,2,;,#endif,它的作用:当标识符在前面已经定义过,则对程序段,1,进行编译,否则对程序段,2,进行编译。,Page,64,预编译处理,b.#ifndef,标识符程序段,1,;,#else,程序段,2,;,#endif,它的作用和,#ifdef,相反,当标识符之前没被定义过,则对程序段,1,进行编译,否则就对程序段,2,进行编译。,Page,65,预编译处理,c.#if,表达式程序段,1,;,#else,程序段,2,;,#endif,它的作用:当表达式的值为真时,对程序段,1,进行编译,都则就对程序段,2,进行编译;,Page,66,预编译处理,举例,下面是某工程中,.h,文件中的一段程序,请说明,#ifndef/#define/#endif,结构的作用。,#ifndefDEF_H#defineDEF_H#include#include“graphics.h”.#endif,防止头文件被重复引用,Page,67,编程规范,Page,68,编程规范,1,)规范的作用,改善代码质量,提高开发速度,增进团队精神,养成良好习惯,Page,69,编程规范,2,)编程规范,养成良好的编程习惯,Page,70,开发高效程序的技巧,Page,71,开发高效程序的技巧,1,),ARM,编程中局部变量的使用,举例,请看一面一段程序:,int checksum(int*data)char i;.for(i=0;i NUM)currentFocus,=0;,LcdDisplay(menucurrentFocus.text);break;,Page,79,开发高效程序的技巧,5,),Bug,的修正,别急着改,想想,再想想,想清楚了再动手;,考虑所做的修改是否对系统造成新的影响;,考虑是否对全局数据结构或其他人的代码造成影响;,修改完了,应该有详细的代码注释和文档,并对修改过的代码进行测试。,Page,80,一些有益的建议,长期坚持好的,Coding Style;,避免编写技巧性很高的代码;,长期坚持良好的文档写作习惯;,不要崇拜那些独来独往、不受约束且带点邪气的所谓“真正的编程高手”;,基础最重要,坚持学习,天天向上。,Page,81,谢谢!,
展开阅读全文