探讨C语言中关键字volatile的含义_C 语言

volatile 的意思是“易失的,易改变的”。这个限定词的含义是向编译器指明变量的内容可能会由于其他程序的修改而变化。通常在程序中申明了一个变量时,编译器会尽量把它存放在通用寄存器中,例如ebx。当CPU把其值放到ebx中后就不会再关心对应内存中的值。若此时其他程序(例如内核程序或一个中断)修改了内存中它的值,ebx中的值并不会随之更新。为了解决这种情况就创建了volatile限定词,让代码在引用该变量时一定要从指定位置取得其值。

关键字volatile有什么含意?并给出三个不同的例子。 一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
1). 并行设备的硬件寄存器(如:状态寄存器)
2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3). 多线程应用中被几个任务共享的变量
回答不出这个问题的人是不会被雇佣的。我认为这是区分C程序员和嵌入式系统程序员的最基本的问题。嵌入式系统程序员经常同硬件、中断、RTOS等等打交道,所用这些都要求volatile变量。不懂得volatile内容将会带来灾难。
假设被面试者正确地回答了这是问题(嗯,怀疑这否会是这样),我将稍微深究一下,看一下这家伙是不是直正懂得volatile完全的重要性。
1). 一个参数既可以是const还可以是volatile吗?解释为什么。
2). 一个指针可以是volatile 吗?解释为什么。
3). 下面的函数有什么错误:
int square(volatile int *ptr)
{return *ptr * *ptr;}
下面是答案:
1). 是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
2). 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。
3). 这段代码的有个恶作剧。这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:

复制代码 代码如下:

int square(volatile int *ptr)
{
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}

由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:

复制代码 代码如下:

long square(volatile int *ptr)
{
int a;
a = *ptr;
return a * a; }

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说明,因为每次对它的读写都可能由不同意义;
另外,以上这几种情况经常还要同时考虑数据的完整性(相互关联的几个标志读了一半被打断了重写),在1中可以通过关中断来实
现,2中可以禁止任务调度,3中则只能依靠硬件的良好设计了。
//=============
指针类型也是一种变量,所以也是可以用volatile来修饰的.
volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如
操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行
优化,从而可以提供对特殊地址的稳定访问。
使用该关键字的例子如下:
int   volatile   nVint;
当要求使用volatile   声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指
令刚刚从该处读取过数据。而且读取的数据立刻被保存。
例如:
volatile   int   i=10;
int   a   =   i;
。。。//其他代码,并未明确告诉编译器,对i进行过操作
int   b   =   i;
volatile   指出   i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,因而编译器生成的
汇编代码会重新从i的地址读取数据放在b中。而优化做法是,由于编译器发现两次从i读数据的代码之间
的代码没有对i进行过操作,它会自动把上次读的数据放在b中。而不是重新从i里面读。这样以来,如果
i是一个寄存器变量或者表示一个端口数据就容易出错,所以说volatile可以保证对特殊地址的稳定访问。
注意,在vc6中,一般调试模式没有进行代码优化,所以这个关键字的作用看不出来。下面通过插入汇编
代码,测试有无volatile关键字,对程序最终代码的影响:
首先用classwizard建一个win32   console工程,插入一个voltest.cpp文件,输入下面的代码:

复制代码 代码如下:

#include   <stdio.h>
void   main()
{
  int   i=10;
  int   a   =   i;
  printf( "i=   %d\n ",a);
                //下面汇编语句的作用就是改变内存中i的值,但是又不让编译器知道
  __asm   {
    mov                   dword   ptr   [ebp-4],   20h
  }
  int   b   =   i;
  printf( "i=   %d\n ",b);
}

然后,在调试版本模式运行程序,输出结果如下:

复制代码 代码如下:

i   =   10
i   =   32

然后,在release版本模式运行程序,输出结果如下:

复制代码 代码如下:

i   =   10
i   =   10

输出的结果明显表明,release模式下,编译器对代码进行了优化,第二次没有输出正确的i值。
下面,我们把   i的声明加上volatile关键字,看看有什么变化:

复制代码 代码如下:

#include   <stdio.h>
void   main()
{
  volatile   int   i=10;
  int   a   =   i;
  printf( "i=   %d\n ",a);
  __asm   {
    mov                   dword   ptr   [ebp-4],   20h
  }
  int   b   =   i;
  printf( "i=   %d\n ",b);
}

分别在调试版本和release版本运行程序,输出都是:

复制代码 代码如下:

i   =   10
i   =   32

这说明这个关键字发挥了它的作用!

时间: 2025-01-31 07:05:28

探讨C语言中关键字volatile的含义_C 语言的相关文章

C语言中正切的相关函数总结_C 语言

C语言tan()函数:正切函数头文件: #include <math.h> tan() 函数用来求给定值的正切值,其原型为: double tan(double x); [参数]x 为给定的弧度值. [返回值]返回 x 的正切值. 注意,使用 GCC 编译时请加入-lm. 请看下面的代码: #include <math.h> main(){ double answer = tan(0.5); printf("tan(0.5) = %f\n", answer);

C语言中进制知识汇总_C 语言

1.什么是进制 进制是一种计数的方式,常用的有二进制.八进制.十进制.十六进制.任何数据在计算机内存中都是以二进制的形式存放的. 我对进制的个人理解,二进制数是以2为计算单元,满2进1位的数:八进制数是以8为计算单元,满8进1位的数. 对于任何一个数字,我们都可以用不同的进制来表示,比如,十进制数12,用二进制表示为1100,用八进制表示为14,用十六进制表示为0xC. 2.进制的转换规则 遵循满进制值进1位,个位数变为0的原理,下面我们以十进制数18为例,对1-18中每一个数值转换各种进制做一

通过一个小例子来简单理解C语言中的内存空间管理_C 语言

对于一个C语言程序而言,内存空间主要由五个部分组成代码段(.text).数据段(.data).BSS段(.bss),堆和栈组成,其中代码段,数据段和BSS段是编译的时候由编译器分配的,而堆和 栈是程序运行的时候由系统分配的.布局如下 在上图中,由编译器分配的地址空间都是在连接的时候分配的,而运行时分配的空间是在程序运行时由系统分配的 BSS段:BSS段(bss segment)通常是指用来存放程序中未初始化的全局变量和静态变量 (这里注意一个问题:一般的书上都会说全局变量和静态变量是会自动初始化

关于C语言中参数的传值问题_C 语言

1. 考题一:程序代码如下: 复制代码 代码如下: void Exchg1(int x, int y)  {   int tmp;   tmp=x;   x=y;   y=tmp;   printf("x=%d,y=%d/n",x,y) } void main() {   int a=4,b=6;   Exchg1 (a,b) ;   printf("a=%d,b=%d/n",a,b) } 输出的结果: x=____, y=____ a=____, b=____ 问

对C语言中递归算法的深入解析_C 语言

许多教科书都把计算机阶乘和菲波那契数列用来说明递归,非常不幸我们可爱的著名的老潭老师的<C语言程序设计>一书中就是从阶乘的计算开始的函数递归.导致读过这本经书的同学们,看到阶乘计算第一个想法就是递归.但是在阶乘的计算里,递归并没有提供任何优越之处.在菲波那契数列中,它的效率更是低的非常恐怖. 这里有一个简单的程序,可用于说明递归.程序的目的是把一个整数从二进制形式转换为可打印的字符形式.例如:给出一个值4267,我们需要依次产生字符'4','2','6',和'7'.就如在printf函数中使用

浅谈C/C++ 语言中的表达式求值_C 语言

经常可以在一些讨论组里看到下面的提问:"谁知道下面C语句给n赋什么值?" m = 1; n = m+++m++; 最近有位不相识的朋友发email给我,问为什么在某个C++系统里,下面表达式打印出两个4,而不是4和5: a = 4; cout << a++ << a; C++ 不是规定 << 操作左结合吗?是C++ 书上写错了,还是这个系统的实现有问题? 注:运行a = 4; cout << a++ << a; 如在Visua

c语言中使用BF-KMP算法实例_C 语言

直接上代码 复制代码 代码如下: #define _CRT_SECURE_NO_WARNINGS#include<stdio.h>#include<stdlib.h>#include<string.h> #define MAX_SIZE 255    //定义字符串的最大长度 typedef unsigned char SString[MAX_SIZE];//数组第一个保存长度//BFint BFMatch(char *s,char *p){    int i,j;  

C语言中关键字auto、static、register、const、volatile、extern的作用

原文:C语言中关键字auto.static.register.const.volatile.extern的作用 关键字auto.static.register.const.volatile.extern 这些关键词都是c++基础知识,我整理了一下,希望对新学的朋友们有用: (1)auto 这个这个关键字用于声明变量的生存期为自动,即将不在任何类.结构.枚举.联合和函数中定义的变量视为全局变量,而在函数中定义的变量视为局部变量.这个关键字不怎么多写,因为所有的变量默认就是auto的. (2)reg

C语言入门之指针用法教程_C 语言

本文针对C语言初学者详细讲述了指针的用法,并配以实例进行说明.具体分析如下: 对于C语言初学者来说,需要明白指针是啥?重点就在一个"指"上.指啥?指的地址.啥地址?内存的地址. 上面说明就是指针的本质了. 这里再详细解释下.数据存起来是要存在内存里面的,就是在内存里圈出一块地,在这块地里放想放的东西.变量关心的是这块地里放的东西,并不关心它在内存的哪里圈的地:而指针则关心这块地在内存的哪个地方,并不关心这块地多大,里面存了什么东西. 指针怎么用呢?下面就是基本用法: int a, b,