天天看点

修饰代码的关键字

volatile
           

volatile的目的是,避免进行默认的优化处理.比如说对于编译器优化的功能,如果从编译器看来,有些多余的代码的话,编译器就会启动优化程序,并删除一些代码,但是这在嵌入式系统中很有可能是关键性的处理,必须不能保证被编译器删掉,所以提供了Volitile来声明,告诉编译器无论如何都不要删掉我。举个例子–■比如说下面条件的一段代码externintevent_flagvoidpoll_event(){

while(event_flag0){

/不操作event_flag/…}…}

我们不再循环中改变这里的event_flag的值,这样的话,event_flag看起来就像是多余的,因此单片机编译器可能把此程序看为下段程序voidpoll_event(){

if(event_flag0){

while(1){

/不对event_flag操作/…}}…}

对于一般的编译器,一般都会把程序优化成上述程序。

这样的优化确实可以提高代码速度,比如while循环中不再需要对条件的判断,所以很快,但是这是正确的吗?

对于单线程的程序,这是没有问题的,因为event_flag就永远不会改变,但是对于多线程程序,RTOS的多任务处理的话,event_flag的值可能被其他线程改变,这样问题就来了,因为被优化的代码并不具备对用event_flag变化的能力。因此导致错误的意想不到的结果,如果此代码在ECU上执行的话,那我们的小命可就有可能没了。。。。为了避免这种情况,我们使用volatile关键字来防止程序被编译器优化。具体的使用方法,我们用下面的程序来说明’externvolatileintevent_flag

这样声明event_flag全局变量的话,就不用担心event_flag被优化掉,程序将按照设计来运行。

■还有一个例子

对于条件分歧以外,还有一下的例子externintp_regster1;externintp_regster2;

voidset_regester2(intval){

/在单片机中,必须进行的设定/*p_register1=1;*p_register2=0;*p_register2=val;*p_register1=0;}

您可能看到p_register1被赋值两次,还有p_register2也是,编译器认为,你怎么这么笨,定义两次,于是就把成程序优化为下面

voidset_regester2(intval){

*p_register2=val;p_register1=0;}

这样的话,我们所规定的程序没有办法设置,可能导致一些想不到的问题。为了回避这个问题,我们必须用Volitile来避免这个问题externvolitileintp_regster1;

externvolitileint*p_regster2;

现在单片机的编译器越来越先进,在很多地方,我们不再需要直接写汇编代码,但是在如果对编译器的优化程序没有深刻的理解,像上面的问题,就很危险,因为嵌入式工作在无人的环境中,因此对于编译器的理解,还有要需要一定程序的学习。

register
           

register:这个关键字请求编译器尽可能的将变量存在CPU内部寄存器中,而不是通过内存寻址访问,以提高效率。注意是尽可能,不是绝对。

因为,如果定义了很多register变量,可能会超过CPU的寄存器个数,超过容量。所以只是可能。

不知道什么是寄存器?那见过太监没有?没有?其实我也没有。没见过不要紧,见过就麻烦大了。_,大家都看过古装戏,那些皇帝们要阅读奏章的时候,大臣总是先将奏章交给皇帝旁边的小太监,小太监呢再交给皇帝同志处理。这个小太监只是个中转站,并无别的功能。

那我们再联想到我们的CPU。CPU 不就是我们的皇帝同志么?大臣就相当于我们的内存,数据从他这拿出来。那小太监就是我们的寄存器了(这里先不考虑CPU 的高速缓存区)。数据从内存里拿出来先放到寄存器,然后CPU 再从寄存器里读取数据来处理,处理完后同样把数据通过寄存器存放到内存里,CPU 不直接和内存打交道。这里要说明的一点是:小太监是主动的从大臣手里接过奏章,然后主动的交给皇帝同志,但寄存器没这么自觉,它从不主动干什么事。一个皇帝可能有好些小太监,那么一个CPU 也可以有很多寄存器,不同型号的CPU 拥有寄存器的数量不一样。

为啥要这么麻烦啊?速度!就是因为速度。寄存器其实就是一块一块小的存储空间,只不过其存取速度要比内存快得多。进水楼台先得月嘛,它离CPU 很近,CPU 一伸手就拿到数据了,比在那么大的一块内存里去寻找某个地址上的数据是不是快多了?

使用register修饰符有几点限制。
           

1.register变量必须是能被CPU所接受的类型。这通常意味着register变量必须是一个单个的值,并且长度应该小于或者等于整型的长度。不过,有些机器的寄存器也能存放浮点数。

2.因为register变量可能不存放在内存中,所以不能用“&”来获取register变量的地址。由于寄存器的数量有限,而且某些寄存器只能接受特定类型的数据(如指针和浮点数),因此真正起作用的register修饰符的数目和类型都依赖于运行程序的机器,而任何多余的register修饰符都将被编译程序所忽略。在某些情况下,把变量保存在寄存器中反而会降低程序的运行速度。因为被占用的寄存器不能再用于其它目的;或者变量被使用的次数不够多,不足以装入和存储变量所带来的额外开销。

3.早期的C编译程序不会把变量保存在寄存器中,除非你命令它这样做,这时register修饰符是C语言的一种很有价值的补充。然而,随着编译程序设计技术的进步,在决定那些变量应该被存到寄存器中时,现在的C编译环境能比程序员做出更好的决定。实际上,许多编译程序都会忽略register修饰符,因为尽管它完全合法,但它仅仅是暗示而不是命令。

static关键字在c语言中比较常用,使用恰当能够大大提高程序的模块化特性,有利于扩展和维护。
           

在程序中使用static

变量

  1. 局部变量

    普通局部变量是再熟悉不过的变量了,在任何一个函数内部定义的变量(不加static修饰符)都属于这个范畴。编译器一般不对普通局部变量进行初始化,也就是说它的值在初始时是不确定的,除非对其显式赋值。

普通局部变量存储于进程栈空间,使用完毕会立即释放。

静态局部变量使用static修饰符定义,即使在声明时未赋初值,编译器也会把它初始化为0。且静态局部变量存储于进程的全局数据区,即使函数返回,它的值也会保持不变。

变量在全局数据区分配内存空间;编译器自动对其初始化

其作用域为局部作用域,当定义它的函数结束时,其作用域随之结束

  1. 全局变量

    全局变量定义在函数体外部,在全局数据区分配存储空间,且编译器会自动对其初始化。

普通全局变量对整个工程可见,其他文件可以使用extern外部声明后直接使用。也就是说其他文件不能再定义一个与其相同名字的变量了(否则编译器会认为它们是同一个变量)。

静态全局变量仅对当前文件可见,其他文件不可访问,其他文件可以定义与其同名的变量,两者互不影响。

函数
           

函数的使用方式与全局变量类似,在函数的返回类型前加上static,就是静态函数。其特性如下:

静态函数只能在声明它的文件中可见,其他文件不能引用该函数

不同的文件可以使用相同名字的静态函数,互不影响

非静态函数可以在另一个文件中直接引用,甚至不必使用extern声明

extern
           

关键字extern,可以在一个文件中引用另一个文件中定义的变量或者函数

extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义

void
           

void类型修饰符(type specifier)表示“没有值可以获得”。因此,不可以采用这个类型声明变量或常量。

void用于函数声明

没有返回值的函数,其类型为 void

参数列表中的关键字 void 表示该函数没有参数

指向void的指针

一个 void* 类型的指针代表了对象的地址,但没有该对象的类型信息。这种“无数据类型”的指针主要用于声明函数,让函数可使用各种类型的指针参数,或者返回一个“多用途”的指针

1)C 语言规定只有相同类型的指针才可以相互赋值

(2)void* 指针作为左值用于“接收”任意类型的指针

(3)void* 指针作为右值使用时需要进行强制类型转换