嵌入式C语言经常使用keyword
2017-06-18 21:39 tlnshuju 阅读(474) 评论(0) 编辑 收藏 举报1.statickeyword
这个keyword前面也有提到。它的作用是强大的。
要对statickeyword深入了解。首先须要掌握标准C程序的组成。
标准C程序一直由下列部分组成:
1)正文段——CPU运行的机器指令部分,也就是你的程序。一个程序仅仅有一个副本;仅仅读,这是为了防止程序因为意外事故而改动自身指令。
2)初始化数据段(数据段)——在程序中全部赋了初值的全局变量。存放在这里。
3)非初始化数据段(bss段)——在程序中没有初始化的全局变量;内核将此段初始化为0。
注意:仅仅有全局变量被分配到数据段中。
4)栈——增长方向:自顶向下增长。自己主动变量以及每次函数调用时所须要保存的信息(返回地址;环境信息)。这句非常关键。经常有笔试题会问到什么东西放到栈里面就足以说明。
5)堆——动态存储分配。
在嵌入式C语言其中,它有三个作用:
作用一:在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。
这样定义的变量称为局部静态变量:在局部变量之前加上keywordstatic,局部变量就被定义成为一个局部静态变量。
也就是上面的作用一中提到的在函数体内定义的变量。除了类型符外,若不加其他keyword修饰,默认都是局部变量。比方下面代码:
void test1(void)
{
unsigned char a;
static unsigned char b。
…
a++。
b++;
}
在这个样例中,变量a是局部变量,变量b为局部静态变量。作用一说明了局部静态变量b的特性:在函数体,一个被声明为静态的变量(也就是局部静态变量)在这一函数被调用过程中维持其值不变。
这句话什么意思呢?若是连续两次调用上面的函数test1:
void main(void)
{
…
test1();
test1()。
…
}
然后使程序暂停下来,读取a和b的值,你会发现,a=1。b=2。怎么回事呢。每次调用test1函数,局部变量a都会又一次初始化为0x00。然后运行a++。而局部静态变量在调用过程中却能维持其值不变。
通常利用这个特性能够统计一个函数被调用的次数。
声明函数的一个局部变量。并设为static类型,作为一个计数器。这样函数每次被调用的时候就能够进行计数。这是统计函数被调用次数的最好的办法,由于这个变量是和函数息息相关的,而函数可能在多个不同的地方被调用。所以从调用者的角度来统计比較困难。
代码例如以下:
void count();
int main()
{
int i;
for (i = 1; i <= 3; i++)
{
count();
{
return 0;
}
void count()
{
static num = 0;
num++;
printf(" I have been called %d",num,"times/n");
}
输出结果为:
I have been called 1 times.
I have been called 2 times.
I have been called 3 times.
看一下局部静态变量的具体特性。注意它的作用域。
1)内存中的位置:静态存储区
2)初始化:未经初始化的全局静态变量会被程序自己主动初始化为0(自己主动对象的值是随意的,除非他被显示初始化)
3)作用域:作用域仍为局部作用域,当定义它的函数或者语句块结束的时候,作用域随之结束。
注:当static用来修饰局部变量的时候。它就改变了局部变量的存储位置,从原来的栈中存放改为静态存储区。可是局部静态变量在离开作用域之后,并没有被销毁,而是仍然驻留在内存其中,直到程序结束,仅仅只是我们不能再对他进行訪问。
作用二:在模块内(但在函数体外)。一个被声明为静态的变量能够被模块内所用函数訪问。但不能被模块外其他函数訪问。它是一个本地的全局变量。
这样定义的变量也称为全局静态变量:在全局变量之前加上keywordstatic。全局变量就被定义成为一个全局静态变量。也就是上述作用二中提到的在模块内(但在函数体外)声明的静态变量。
定义全局静态变量的优点:
<1>不会被其它文件所訪问,改动,是一个本地的局部变量。
<2>其它文件里能够使用同样名字的变量。不会发生冲突。
全局变量的具体特性。注意作用域,能够和局部静态变量相比較:
1)内存中的位置:静态存储区(静态存储区在整个程序执行期间都存在)
2)初始化:未经初始化的全局静态变量会被程序自己主动初始化为0(自己主动对象的值是随意的。除非他被显示初始化)
3)作用域:全局静态变量在声明他的文件之外是不可见的。
准确地讲从定义之处開始到文件结尾。
当static用来修饰全局变量的时候。它就改变了全局变量的作用域(在声明他的文件之外是不可见的)。可是没有改变它的存放位置,还是在静态存储区中。
作用三:在模块内,一个被声明为静态的函数仅仅可被这一模块内的其他函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用。
这样定义的函数也成为静态函数:在函数的返回类型前加上keywordstatic,函数就被定义成为静态函数。
函数的定义和声明默认情况下是extern的,但静态函数仅仅是在声明他的文件其中可见,不能被其它文件所用。
定义静态函数的优点:
<1> 其它文件里能够定义同样名字的函数。不会发生冲突
<2> 静态函数不能被其它文件所用。
它定义一个本地的函数。
这里我一直强调数据和函数的本地化,这对于程序的结构甚至优化都有巨大的优点,更大的作用是,本地化的数据和函数能给人传递非常多实用的信息。能约束数据和函数的作用范围。在C++的对象和类中非常注重的私有和公共数据/函数事实上就是本地和全局数据/函数的扩展,这也从側面反应了本地化数据/函数的优势。
最后说一下存储说明符,在标准C语言中。存储说明符有下面几类:
auto、register、extern和static
相应两种存储期:自己主动存储期和静态存储期。
auto和register相应自己主动存储期。具有自己主动存储期的变量在进入声明该变量的程序块时被建立,它在该程序块活动时存在。退出该程序块时撤销。
keywordextern和static用来说明具有静态存储期的变量和函数。用static声明的局部变量具有静态存储持续期(static storage duration),或静态范围(static extent)。尽管他的值在函数调用之间保持有效。可是其名字的可视性仍限制在其局部域内。静态局部对象在程序运行到该对象的声明处时被首次初始化。
2. const keyword
constkeyword也是一个优秀程序中经经常使用到的keyword。
keywordconst 的作用是为给读你代码的人传达非常实用的信息,实际上,声明一个參数为常量是为了告诉了用户这个參数的应用目的。通过给优化器一些附加的信息,使用keywordconst或许能产生更紧凑的代码。合理地使用keywordconst 能够使编译器非常自然地保护那些不希望被改变的參数。防止其被无意的代码改动。简而言之。这样能够降低bug的出现。
深入理解constkeyword,你必须知道:
a. constkeyword修饰的变量能够觉得有仅仅读属性,但它绝不与常量划等号。例如以下代码:
const int i=5;
int j=0;
...
i=j; //非法,导致编译错误,由于仅仅能被读
j=i; //合法
b. constkeyword修饰的变量在声明时必须进行初始化。例如以下代码:
const int i=5; //合法
const int j; //非法,导致编译错误
c. 用const声明的变量尽管添加了分配空间。可是能够保证类型安全。const最初是从C++变化得来的,它能够替代define来定义常量。在旧版本号(标准前)的c中,假设想建立一个常量,必须使用预处理器:
#define PI 3.14159
此后不管在何处使用PI,都会被预处理器以3.14159替代。
编译器不正确PI进行类型检查,也就是说能够不受限制的建立宏并用它来替代值,假设使用不慎。非常可能由预处理引入错误,这些错误往往非常难发现。并且,我们也不能得到PI的地址(即不能向PI传递指针和引用)。
const的出现,比較好的攻克了上述问题。
d. C标准中,const定义的常量是全局的。
e. 必须明确以下语句的含义。我自己是重复记忆了许久才记住。方法是:若是想定义一个仅仅读属性的指针。那么keywordconst要放到‘* ’后面。
char *const cp; //指针不可改变。但指向的内容能够改变
char const *pc1; //指针能够改变,但指向的内容不能改变
const char *pc2; //同上(后两个声明是等同的)
f. 将函数传入參数声明为const。以指明使用这样的參数不过为了效率的原因,而不是想让调用函数可以改动对象的值。
參数const通经常使用于參数为指针或引用的情况,且仅仅能修饰输入參数;若输入參数採用“值传递”方式,因为函数将自己主动产生暂时变量用于复制该參数,该參数本就不须要保护,所以不用const修饰。
样例:
void fun0(const int * a );
void fun1(const int & a);
调用函数的时候,用对应的变量初始化const常量。则在函数体中,依照const所修饰的部分进行常量化,如形參为const int * a。则不能对传递进来的指针所指向的内容进行改变,保护了原指针所指向的内容。如形參为const int & a,则不能对传递进来的引用对象进行改变。保护了原对象的属性。
g. 修饰函数返回值,能够阻止用户改动返回值。(在嵌入式C中一般不用。主要用于C++)
h. const消除了预处理器的值替代的不良影响,而且提供了良好的类型检查形式和安全性。在可能的地方尽可能的使用const对我们的编程有非常大的帮助。前提是:你对const有了足够的理解。
最后,举两个经常使用的标准C库函数声明。它们都是使用const的典范。
1.字符串拷贝函数:char *strcpy(char *strDest,const char *strSrc)。
2.返回字符串长度函数:int strlen(const char *str);
3. volatilekeyword
一个定义为volatile 的变量是说这变量可能会被意想不到地改变,这样。编译器就不会去如果这个变量的值了。
精确地说就是,优化器在用到这个变量时必须每次都小心地又一次读取这个变量的值。而不是使用保存在寄存器里的备份。
因为訪问寄存器的速度要快过RAM,所以编译器一般都会作降低存取外部RAM的优化。比方:
static int i=0;
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推断都仅仅使用这个寄存器里面的“i副本”,导致dosomething永远也不会被调用。
假设将将变量加上volatile修饰,则编译器保证对此变量的读写操作都不会被优化(肯定运行)。此例中i也应该如此说明。
一般说来,volatile用在例如以下的几个地方:
1、中断服务程序中改动的供其他程序检測的变量须要加volatile;
2、多任务环境下各任务间共享的标志应该加volatile;
3、存储器映射的硬件寄存器通常也要加volatile说明。由于每次对它的读写都可能有不允许义。
不懂得volatile 的内容将会带来灾难。这也是区分C语言和嵌入式C语言程序猿的一个关键因素。为强调volatile的重要性,再次举例分析:
代码一:
int a,b,c;
//读取I/O空间0x100port的内容
a= inword(0x100);
b=a;
a=inword(0x100)
c=a;
代码二:
volatile int a;
int a,b,c;
//读取I/O空间0x100port的内容
a= inword(0x100);
b=a;
a=inword(0x100)
c=a;
在上述样例中,代码一会被绝大多数编译器优化为例如以下代码:
a=inword(0x100)
b=a;
c=a;
这显然与编写者的目的不相符,会出现I/O空间0x100port漏读现象,若是添加volatile,像代码二所看到的的那样,优化器将不会优化掉不论什么代码.
从上面来看,volatilekeyword是会减少编译器优化力度的,但它保证了程序的正确性,所以在适合的地方使用keywordvolatile是件考验编程功底的事情.
4.struct与typedefkeyword
面对一个人的大型C/C++程序时。仅仅看其对struct的使用情况我们就能够对其编写者的编程经验进行评估。由于一个大型的C/C++程序,势必要涉及一些(甚至大量)进行数据组合的结构体,这些结构体能够将原本意义属于一个总体的数据组合在一起。
从某种程度上来说。会不会用struct。如何用struct是差别一个开发者是否具备丰富开发经历的标志。
在网络协议、通信控制、嵌入式系统的C/C++编程中。我们常常要传送的不是简单的字节流(char型数组),而是多种数据组合起来的一个总体,其表现形式是一个结构体。
经验不足的开发者往往将全部须要传送的内容依顺序保存在char型数组中,通过指针偏移的方法传送网络报文等信息。这样做编程复杂。易出错,并且一旦控制方式及通信协议有所变化,程序就要进行很仔细的改动。
使用方法:
在C中定义一个结构体类型要用typedef:
typedef struct Student
{
int a;
}Stu;
于是在声明变量的时候就可:Stu stu1;
假设没有typedef就必须用struct Student stu1;来声明
这里的Stu实际上就是struct Student的别名。
另外这里也能够不写Student(于是也不能struct Student stu1;了)
typedef struct
{
int a;
}Stu;
structkeyword的一个总要作用是它能够实现对数据的封装,有一点点类似与C++的对象,能够将一些分散的特性对象化,这在编写某些复杂程序时提供非常大的方便性.
比方编写一个菜单程序,你要知道本级菜单的菜单索引號、焦点在屏上是第几项、显示第一项相应的菜单栏目索引、菜单文本内容、子菜单索引、当前菜单运行的功能操作。
若是对上述条目单独操作。那么程序的复杂程度将会大到不可想象。若是菜单层数少些还easy实现。一旦菜单层数超出四层,呃~我就没法形容了。若是有编写过菜单程序的朋友也许理解非常深。
这时候结构体struct就開始显现它的威力了:
//结构体定义
typedef struct
{
unsigned char CurrentPanel;//本级菜单的菜单索引號
unsigned char ItemStartDisplay; //显示第一项相应的菜单栏目索引
unsigned char FocusLine; //焦点在屏上是第几项
}Menu_Statestruct;
typedef struct
{
unsigned char *MenuTxt; //菜单文本内容
unsigned char MenuChildID;//子菜单索引
void (*CurrentOperate)();//当前菜单运行的功能操作
}MenuItemStruct;
typedef struct
{
MenuItemStruct *MenuPanelItem;
unsigned char MenuItemCount;
}MenuPanelStruct;